在表中設(shè)計一個版本字段 version,第一次讀的時候,同時會獲取 version 字段的取值。然后對數(shù)據(jù)進行更新或刪除操作時,會執(zhí)行UPDATE ... SET version=version+1 WHERE version=version。此時如果已經(jīng)有事務(wù)對這條數(shù)據(jù)進行了更改,修改就不會成功。
T1: select * from table (請想象它需要執(zhí)行1個小時之久,后面的sql語句請都這么想象)
T2: update table set column1='hello'過程:T1運行 (加共享鎖)T2運行
If T1 還沒執(zhí)行完T2等......else鎖被釋放T2執(zhí)行
endifT2之所以要等,是因為T2在執(zhí)行update前,試圖對table表加一個排他鎖,
而數(shù)據(jù)庫規(guī)定同一資源上不能同時共存共享鎖和排他鎖。所以T2必須等T1
執(zhí)行完,釋放了共享鎖,才能加上排他鎖,然后才能開始執(zhí)行update語句。
例2:
T1: select * from table
T2: select * from table這里T2不用等待T1執(zhí)行完,而是可以馬上執(zhí)行。分析:
T1運行,則table被加鎖,比如叫l(wèi)ockA
T2運行,再對table加一個共享鎖,比如叫l(wèi)ockB。兩個鎖是可以同時存在于同一資源上的(比如同一個表上)。這被稱為共
享鎖與共享鎖兼容。這意味著共享鎖不阻止其它session同時讀資源,但阻
止其它session update
例3:
T1: select * from table
T2: select * from table
T3: update table set column1='hello'這次,T2不用等T1運行完就能運行,T3卻要等T1和T2都運行完才能運行。
因為T3必須等T1和T2的共享鎖全部釋放才能進行加排他鎖然后執(zhí)行update
操作。
例4:(死鎖的發(fā)生)
T1:
begin transelect * from table (holdlock)(holdlock意思是加共享鎖,直到事物結(jié)束才釋放)update table set column1='hello'T2:
begin transelect * from table(holdlock)update table set column1='world'假設(shè)T1和T2同時達到select,T1對table加共享鎖,T2也對加共享鎖,當
T1的select執(zhí)行完,準備執(zhí)行update時,根據(jù)鎖機制,T1的共享鎖需要升
級到排他鎖才能執(zhí)行接下來的update.在升級排他鎖前,必須等table上的
其它共享鎖釋放,但因為holdlock這樣的共享鎖只有等事務(wù)結(jié)束后才釋放,
所以因為T2的共享鎖不釋放而導(dǎo)致T1等(等T2釋放共享鎖,自己好升級成排
他鎖),同理,也因為T1的共享鎖不釋放而導(dǎo)致T2等。死鎖產(chǎn)生了。
例5:
T1:
begin tranupdate table set column1='hello' where id=10T2:
begin tranupdate table set column1='world' where id=20這種語句雖然最為常見,很多人覺得它有機會產(chǎn)生死鎖,但實際上要看情
況,如果id是主鍵上面有索引,那么T1會一下子找到該條記錄(id=10的記
錄),然后對該條記錄加排他鎖,T2,同樣,一下子通過索引定位到記錄,
然后對id=20的記錄加排他鎖,這樣T1和T2各更新各的,互不影響。T2也不
需要等。但如果id是普通的一列,沒有索引。那么當T1對id=10這一行加排他鎖后,
T2為了找到id=20,需要對全表掃描,那么就會預(yù)先對表加上共享鎖或更新
鎖或排他鎖(依賴于數(shù)據(jù)庫執(zhí)行策略和方式,比如第一次執(zhí)行和第二次執(zhí)行
數(shù)據(jù)庫執(zhí)行策略就會不同)。但因為T1已經(jīng)為一條記錄加了排他鎖,導(dǎo)致
T2的全表掃描進行不下去,就導(dǎo)致T2等待。死鎖怎么解決呢?一種辦法是,如下:
例6:
T1:
begin transelect * from table(xlock)(xlock意思是直接對表加排他鎖)update table set column1='hello'T2:
begin transelect * from table(xlock)update table set column1='world'這樣,當T1的select 執(zhí)行時,直接對表加上了排他鎖,T2在執(zhí)行select時,就需要等T1事物完全執(zhí)行完才能執(zhí)行。排除了死鎖發(fā)生。
但當?shù)谌齻€user過來想執(zhí)行一個查詢語句時,也因為排他鎖的存在而不得不等待,第四個、第五個user也會因此而等待。在大并發(fā)
情況下,讓大家等待顯得性能就太友好了,所以,這里引入了更新鎖。
更新鎖(Update lock)
為解決死鎖,引入更新鎖。
例7:
T1:
begin transelect * from table(updlock)(加更新鎖)update table set column1='hello'T2:
begin transelect * from table(updlock)update table set column1='world'更新鎖的意思是:“我現(xiàn)在只想讀,你們別人也可以讀,但我將來可能會做更新操作,
我已經(jīng)獲取了從共享鎖(用來讀)到排他鎖(用來更新)的資格”。一個事物只能有
一個更新鎖獲此資格。T1執(zhí)行select,加更新鎖。
T2運行,準備加更新鎖,但發(fā)現(xiàn)已經(jīng)有一個更新鎖在那兒了,只好等。當后來有user3、user4...需要查詢table表中的數(shù)據(jù)時,并不會因為T1的
select在執(zhí)行就被阻塞,照樣能查詢,相比起例6,這提高了效率。
例8:
T1: select * from table(updlock)(加更新鎖)T2: select * from table(updlock)(等待,直到T1釋放更新鎖,因為同一時間不能在同一資源上有兩個更新鎖)T3: select * from table (加共享鎖,但不用等updlock釋放,就可以讀)這個例子是說明:共享鎖和更新鎖可以同時在同一個資源上。這被稱為共享鎖和更新鎖是兼容的。
例9:
T1:
beginselect * from table(updlock)(加更新鎖)update table set column1='hello'(重點:這里T1做update時,不需要等T2釋放什么,而是直接把更新鎖升級為排他鎖,然后執(zhí)行update)T2:
beginselect * from table (T1加的更新鎖不影響T2讀取)update table set column1='world'(T2的update需要等T1的update做完才能執(zhí)行)我們以這個例子來加深更新鎖的理解,第一種情況:T1先達,T2緊接到達;在這種情況中,T1先對表加更新鎖,T2對表加共享鎖,
假設(shè)T2的select先執(zhí)行完,準備執(zhí)行update,發(fā)現(xiàn)已有更新鎖存在,T2等。T1執(zhí)行
這時才執(zhí)行完select,準備執(zhí)行update,更新鎖升級為排他鎖,然后執(zhí)行update,執(zhí)行完成,事務(wù)結(jié)束,釋放鎖,T2才輪到執(zhí)行update。第二種情況:T2先達,T1緊接達;在這種情況,T2先對表加共享鎖,T1達后,T1
對表加更新鎖,假設(shè)T2 select先結(jié)束,準備update,發(fā)現(xiàn)已有更新鎖,則等待,
后面步驟就跟第一種情況一樣了。這個例子是說明:排他鎖與更新鎖是不兼容的,它們不能同時加在同一子資源上。
排他鎖(獨占鎖,Exclusive Locks)
這個簡單,即其它事務(wù)既不能讀,又不能改排他鎖鎖定的資源。
例10
T1: update table set column1='hello' where id<1000
T2: update table set column1='world' where id>1000假設(shè)T1先達,T2隨后至,這個過程中T1會對id<1000的記錄施加排他鎖.
但不會阻塞T2的update。
例11 (假設(shè)id都是自增長且連續(xù)的)
T1: update table set column1='hello' where id<1000
T2: update table set column1='world' where id>900如同例10,T1先達,T2立刻也到,T1加的排他鎖會阻塞T2的update.
T1:
begin transelect * from table (xlock) where id=10--意思是對id=10這一行強加排他鎖T2:
begin transelect * from table (tablock)--意思是要加表級鎖假設(shè)T1先執(zhí)行,T2后執(zhí)行,T2執(zhí)行時,欲加表鎖,為判斷是否可以加表鎖,數(shù)據(jù)庫
系統(tǒng)要逐條判斷table表每行記錄是否已有排他鎖,如果發(fā)現(xiàn)其中一行已經(jīng)有排他鎖
了,就不允許再加表鎖了。只是這樣逐條判斷效率太低了。實際上,數(shù)據(jù)庫系統(tǒng)不是這樣工作的。當T1的select執(zhí)行時,系統(tǒng)對表table的
id=10的這一行加了排他鎖,還同時悄悄的對整個表加了意向排他鎖(IX),
當T2執(zhí)行表鎖時,只需要看到這個表已經(jīng)有意向排他鎖存在,就直接等待,而不
需要逐條檢查資源了。
例13:
T1:
begin tranupdate table set column1='hello' where id=1T2:
begin tranupdate table set column1='world' where id=1這個例子和上面的例子實際效果相同,T1執(zhí)行,系統(tǒng)對table同時對行家排他鎖、
對頁加意向排他鎖、對表加意向排他鎖。
計劃鎖(Schema Locks)
例14:
alter table ....(加schema locks,稱之為Schema modification (Sch-M) locksDDL語句都會加Sch-M鎖
該鎖不允許任何其它session連接該表。連都連不了這個表了,當然更不用說想對
該表執(zhí)行什么sql語句了。
例15:
用jdbc向數(shù)據(jù)庫發(fā)送了一條新的sql語句,數(shù)據(jù)庫要先對之進行編譯,
在編譯期間,也會加鎖,稱之為:Schema stability (Sch-S) locksselect * from tableA編譯這條語句過程中,其它session可以對表tableA做任何操作
(update,delete,加排他鎖等等),但不能做DDL(比如alter table)操作。