數據庫鎖的幾種類型(保存)

轉載自:數據庫鎖的幾種類型 - CSDN博客

1前言

數據庫大并發操作要考慮死鎖和鎖的性能問題。看到網上大多語焉不詳(尤其更新鎖),所以這里做個簡明解釋,為下面描述方便,這里用T1代表一個數據庫執行請求,T2代表另一個請求,也可以理解為T1為一個線程,T2 為另一個線程。T3,T4以此類推。下面以SQL Server(2005)為例。

2鎖的種類

共享鎖(Shared lock)。

例1:

----------------------------------------

T1:? ? select * from table (請想象它需要執行1個小時之久,后面的sql語句請都這么想象)

T2:? ? update table set column1='hello'

過程:

T1運行 (加共享鎖)

T2運行

If T1 還沒執行完

? ? T2等......

else

? ? 鎖被釋放

? ? T2執行

endif

T2之所以要等,是因為T2在執行update前,試圖對table表加一個排他鎖,

而數據庫規定同一資源上不能同時共存共享鎖和排他鎖。所以T2必須等T1

執行完,釋放了共享鎖,才能加上排他鎖,然后才能開始執行update語句。

例2:

----------------------------------------

T1:? ? select * from table

T2:? ? select * from table

這里T2不用等待T1執行完,而是可以馬上執行。

分析:

T1運行,則table被加鎖,比如叫lockA

T2運行,再對table加一個共享鎖,比如叫lockB。

兩個鎖是可以同時存在于同一資源上的(比如同一個表上)。這被稱為共

享鎖與共享鎖兼容。這意味著共享鎖不阻止其它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的共享鎖全部釋放才能進行加排他鎖然后執行update

操作。

例4:(死鎖的發生)

----------------------------------------

T1:

begin tran

select * from table (holdlock) (holdlock意思是加共享鎖,直到事物結束才釋放)

update table set column1='hello'

T2:

begin tran

select * from table(holdlock)

update table set column1='world'

假設T1和T2同時達到select,T1對table加共享鎖,T2也對加共享鎖,當

T1的select執行完,準備執行update時,根據鎖機制,T1的共享鎖需要升

級到排他鎖才能執行接下來的update.在升級排他鎖前,必須等table上的

其它共享鎖釋放,但因為holdlock這樣的共享鎖只有等事務結束后才釋放,

所以因為T2的共享鎖不釋放而導致T1等(等T2釋放共享鎖,自己好升級成排

他鎖),同理,也因為T1的共享鎖不釋放而導致T2等。死鎖產生了。

例5:

----------------------------------------

T1:

begin tran

update table set column1='hello' where id=10

T2:

begin tran

update table set column1='world' where id=20

這種語句雖然最為常見,很多人覺得它有機會產生死鎖,但實際上要看情

況,如果id是主鍵上面有索引,那么T1會一下子找到該條記錄(id=10的記

錄),然后對該條記錄加排他鎖,T2,同樣,一下子通過索引定位到記錄,

然后對id=20的記錄加排他鎖,這樣T1和T2各更新各的,互不影響。T2也不

需要等。

但如果id是普通的一列,沒有索引。那么當T1對id=10這一行加排他鎖后,

T2為了找到id=20,需要對全表掃描,那么就會預先對表加上共享鎖或更新

鎖或排他鎖(依賴于數據庫執行策略和方式,比如第一次執行和第二次執行

數據庫執行策略就會不同)。但因為T1已經為一條記錄加了排他鎖,導致

T2的全表掃描進行不下去,就導致T2等待。

死鎖怎么解決呢?一種辦法是,如下:

例6:

----------------------------------------

T1:

begin tran

select * from table(xlock) (xlock意思是直接對表加排他鎖)

update table set column1='hello'

T2:

begin tran

select * from table(xlock)

update table set column1='world'

這樣,當T1的select 執行時,直接對表加上了排他鎖,T2在執行select時,就需要等T1事物完全執行完才能執行。排除了死鎖發生。

但當第三個user過來想執行一個查詢語句時,也因為排他鎖的存在而不得不等待,第四個、第五個user也會因此而等待。在大并發

情況下,讓大家等待顯得性能就太友好了,所以,這里引入了更新鎖。

更新鎖(Update lock)

為解決死鎖,引入更新鎖。

例7:

----------------------------------------

T1:

begin tran

select * from table(updlock) (加更新鎖)

update table set column1='hello'

T2:

begin tran

select * from table(updlock)

update table set column1='world'

更新鎖的意思是:“我現在只想讀,你們別人也可以讀,但我將來可能會做更新操作,我已經獲取了從共享鎖(用來讀)到排他鎖

(用來更新)的資格”。一個事物只能有一個更新鎖獲此資格。

T1執行select,加更新鎖。

T2運行,準備加更新鎖,但發現已經有一個更新鎖在那兒了,只好等。

當后來有user3、user4...需要查詢table表中的數據時,并不會因為T1的select在執行就被阻塞,照樣能查詢,相比起例6,這提高

了效率。

例8:

----------------------------------------

T1:? ? select * from table(updlock)? ? (加更新鎖)

T2:? ? select * from table(updlock)? ? (等待,直到T1釋放更新鎖,因為同一時間不能在同一資源上有兩個更新鎖)

T3:? ? select * from table (加共享鎖,但不用等updlock釋放,就可以讀)

這個例子是說明:共享鎖和更新鎖可以同時在同一個資源上。這被稱為共享鎖和更新鎖是兼容的。

例9:

----------------------------------------

T1:

begin

select * from table(updlock)? ? ? (加更新鎖)

update table set column1='hello'? (重點:這里T1做update時,不需要等T2釋放什么,而是直接把更新鎖升級為排他鎖,然后執行update)

T2:

begin

select * from table? ? ? ? ? ? ? (T1加的更新鎖不影響T2讀取)

update table set column1='world'? (T2的update需要等T1的update做完才能執行)

我們以這個例子來加深更新鎖的理解,

第一種情況:T1先達,T2緊接到達;在這種情況中,T1先對表加更新鎖,T2對表加共享鎖,假設T2的select先執行完,準備執行update,

發現已有更新鎖存在,T2等。T1執行這時才執行完select,準備執行update,更新鎖升級為排他鎖,然后執行update,執行完成,事務

結束,釋放鎖,T2才輪到執行update。

第二種情況:T2先達,T1緊接達;在這種情況,T2先對表加共享鎖,T1達后,T1對表加更新鎖,假設T2 select先結束,準備

update,發現已有更新鎖,則等待,后面步驟就跟第一種情況一樣了。

這個例子是說明:排他鎖與更新鎖是不兼容的,它們不能同時加在同一子資源上。

排他鎖(獨占鎖,Exclusive Locks)

這個簡單,即其它事務既不能讀,又不能改排他鎖鎖定的資源。

例10

T1:? ? update table set column1='hello' where id<1000

T2:? ? update table set column1='world' where id>1000

假設T1先達,T2隨后至,這個過程中T1會對id<1000的記錄施加排他鎖.但不會阻塞T2的update。

例11 (假設id都是自增長且連續的)

T1:? ? update table set column1='hello' where id<1000

T2:? ? update table set column1='world' where id>900

如同例10,T1先達,T2立刻也到,T1加的排他鎖會阻塞T2的update.

意向鎖(Intent Locks)

意向鎖就是說在屋(比如代表一個表)門口設置一個標識,說明屋子里有人(比如代表某些記錄)被鎖住了。另一個人想知道屋子

里是否有人被鎖,不用進屋子里一個一個的去查,直接看門口標識就行了。

當一個表中的某一行被加上排他鎖后,該表就不能再被加表鎖。數據庫程序如何知道該表不能被加表鎖?一種方式是逐條的判斷該

表的每一條記錄是否已經有排他鎖,另一種方式是直接在表這一層級檢查表本身是否有意向鎖,不需要逐條判斷。顯然后者效率高。

例12:

----------------------------------------

T1:? ? begin tran

? ? ? select * from table (xlock) where id=10? --意思是對id=10這一行強加排他鎖

T2:? ? begin tran

? ? ? select * from table (tablock)? ? --意思是要加表級鎖


假設T1先執行,T2后執行,T2執行時,欲加表鎖,為判斷是否可以加表鎖,數據庫系統要逐條判斷table表每行記錄是否已有排他鎖,

如果發現其中一行已經有排他鎖了,就不允許再加表鎖了。只是這樣逐條判斷效率太低了。

實際上,數據庫系統不是這樣工作的。當T1的select執行時,系統對表table的id=10的這一行加了排他鎖,還同時悄悄的對整個表

加了意向排他鎖(IX),當T2執行表鎖時,只需要看到這個表已經有意向排他鎖存在,就直接等待,而不需要逐條檢查資源了。

例13:

----------------------------------------

T1:? ? begin tran

? ? ? update table set column1='hello' where id=1

T2:? ? begin tran

? ? ? update table set column1='world' where id=1

這個例子和上面的例子實際效果相同,T1執行,系統對table同時對行家排他鎖、對頁加意向排他鎖、對表加意向排他鎖。

計劃鎖(Schema Locks)

例14:

----------------------------------------

alter table .... (加schema locks,稱之為Schema modification (Sch-M) locks

DDL語句都會加Sch-M鎖

該鎖不允許任何其它session連接該表。連都連不了這個表了,當然更不用說想對該表執行什么sql語句了。

例15:

----------------------------------------

用jdbc向數據庫發送了一條新的sql語句,數據庫要先對之進行編譯,在編譯期間,也會加鎖,稱之為:Schema stability (Sch-S) locks

select * from tableA

編譯這條語句過程中,其它session可以對表tableA做任何操作(update,delete,加排他鎖等等),但不能做DDL(比如alter table)操作。

Bulk Update Locks 主要在批量導數據時用(比如用類似于oracle中的imp/exp的bcp命令)。不難理解,程序員往往也不需要關心,不贅述了。

3何時加鎖?

如何加鎖,何時加鎖,加什么鎖,你可以通過hint手工強行指定,但大多是數據庫系統自動決定的。這就是為什么我們可以不懂鎖也可

以高高興興的寫SQL。

例15:

----------------------------------------

T1:? ? begin tran

? ? ? update table set column1='hello' where id=1

T2:? ? SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED? -- 事物隔離級別為允許臟讀

? ? ? go

? ? ? select * from table where id=1

這里,T2的select可以查出結果。如果事物隔離級別不設為臟讀,則T2會等T1事物執行完才能讀出結果。

數據庫如何自動加鎖的?

1) T1執行,數據庫自動加排他鎖

2) T2執行,數據庫發現事物隔離級別允許臟讀,便不加共享鎖。不加共享鎖,則不會與已有的排他鎖沖突,所以可以臟讀。

例16:

----------------------------------------

T1:? ? begin tran

? ? ? update table set column1='hello' where id=1

T2:? ? select * from table where id=1 --為指定隔離級別,則使用系統默認隔離級別,它不允許臟讀

如果事物級別不設為臟讀,則:

1) T1執行,數據庫自動加排他鎖

2) T2執行,數據庫發現事物隔離級別不允許臟讀,便準備為此次select過程加共享鎖,但發現加不上,因為已經有排他鎖了,所以就

? 等啊等。直到T1執行完,釋放了排他鎖,T2才加上了共享鎖,然后開始讀....

4鎖的粒度

鎖的粒度就是指鎖的生效范圍,就是說是行鎖,還是頁鎖,還是整表鎖. 鎖的粒度同樣既可以由數據庫自動管理,也可以通過手工指定hint來管理。

例17:

----------------------------------------

T1:? ? select * from table (paglock)

T2:? ? update table set column1='hello' where id>10

T1執行時,會先對第一頁加鎖,讀完第一頁后,釋放鎖,再對第二頁加鎖,依此類推。假設前10行記錄恰好是一頁(當然,一般不可能

一頁只有10行記錄),那么T1執行到第一頁查詢時,并不會阻塞T2的更新。

例18:

----------------------------------------

T1:? ? select * from table (rowlock)

T2:? ? update table set column1='hello' where id=10

T1執行時,對每行加共享鎖,讀取,然后釋放,再對下一行加鎖;T2執行時,會對id=10的那一行試圖加鎖,只要該行沒有被T1加上行鎖,

T2就可以順利執行update操作。

例19:

----------------------------------------

T1:? ? select * from table (tablock)

T2:? ? update table set column1='hello' where id = 10

T1執行,對整個表加共享鎖. T1必須完全查詢完,T2才可以允許加鎖,并開始更新。

以上3例是手工指定鎖的粒度,也可以通過設定事物隔離級別,讓數據庫自動設置鎖的粒度。不同的事物隔離級別,數據庫會有不同的

加鎖策略(比如加什么類型的鎖,加什么粒度的鎖)。具體請查聯機手冊。

5鎖與事物隔離級別的優先級

手工指定的鎖優先,

例20:

----------------------------------------

T1:? ? GO

? ? ? SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

? ? ? GO

? ? ? BEGIN TRANSACTION

? ? ? SELECT * FROM table (NOLOCK)

? ? ? GO

T2:? ? update table set column1='hello' where id=10

T1是事物隔離級別為最高級,串行鎖,數據庫系統本應對后面的select語句自動加表級鎖,但因為手工指定了NOLOCK,所以該select

語句不會加任何鎖,所以T2也就不會有任何阻塞。

6數據庫的其它重要Hint以及它們的區別

1) holdlock 對表加共享鎖,且事物不完成,共享鎖不釋放。

2) tablock? 對表加共享鎖,只要statement不完成,共享鎖不釋放。

? 與holdlock區別,見下例:

? 例21

? ----------------------------------------

? T1:

? begin tran

? select * from table (tablock)

? T2:

? begin tran

? update table set column1='hello' where id = 10

? T1執行完select,就會釋放共享鎖,然后T2就可以執行update. 此之謂tablock. 下面我們看holdlock

? 例22

? ----------------------------------------

? T1:

? begin tran

? select * from table (holdlock)

? T2:

? begin tran

? update table set column1='hello' where id = 10


? T1執行完select,共享鎖仍然不會釋放,仍然會被hold(持有),T2也因此必須等待而不能update. 當T1最后執行了commit或

? rollback說明這一個事物結束了,T2才取得執行權。


3) TABLOCKX 對表加排他鎖


? 例23:

? ----------------------------------------

? T1:? ? select * from table(tablockx) (強行加排他鎖)

? 其它session就無法對這個表進行讀和更新了,除非T1執行完了,就會自動釋放排他鎖。

? 例24:

? ----------------------------------------

? T1:? ? begin tran

? ? ? ? ? select * from table(tablockx)

? 這次,單單select執行完還不行,必須整個事物完成(執行了commit或rollback后)才會釋放排他鎖。


4) xlock 加排他鎖

? 那它跟tablockx有何區別呢?

? 它可以這樣用,

? 例25:

? ----------------------------------------

? select * from table(xlock paglock) 對page加排他鎖

? 而TABLELOCX不能這么用。

? xlock還可這么用:select * from table(xlock tablock) 效果等同于select * from table(tablockx)

7鎖的超時等待

例26

SET LOCK_TIMEOUT 4000 用來設置鎖等待時間,單位是毫秒,4000意味著等待

4秒可以用select @@LOCK_TIMEOUT查看當前session的鎖超時設置。-1 意味著

永遠等待。

T1: begin tran

? ? udpate table set column1='hello' where id = 10

T2: set lock_timeout 4000

? ? select * from table wehre id = 10

T2執行時,會等待T1釋放排他鎖,等了4秒鐘,如果T1還沒有釋放排他鎖,T2就會拋出異常: Lock request time out period exceeded.

8附:各種鎖的兼容關系表

| Requested mode? ? ? ? ? ? ? ? ? ? | IS? | S? | U? | IX? | SIX | X? |

| Intent shared (IS)? ? ? ? ? ? ? ? | Yes | Yes | Yes | Yes | Yes | No |

| Shared (S)? ? ? ? ? ? ? ? ? ? ? ? | Yes | Yes | Yes | No? | No? | No |

| Update (U)? ? ? ? ? ? ? ? ? ? ? ? | Yes | Yes | No? | No? | No? | No |

| Intent exclusive (IX)? ? ? ? ? ? ? | Yes | No? | No? | Yes | No? | No |

| Shared with intent exclusive (SIX) | Yes | No? | No? | No? | No? | No |

| Exclusive (X)? ? ? ? ? ? ? ? ? ? ? | No? | No? | No? | No? | No? | No |

9如何提高并發效率

悲觀鎖:利用數據庫本身的鎖機制實現。通過上面對數據庫鎖的了解,可以根據具體業務情況綜合使用事務隔離級別與合理的手工指定鎖的方式比如降低鎖的粒度等減少并發等待。

樂觀鎖:利用程序處理并發。原理都比較好理解,基本一看即懂。方式大概有以下3種

對記錄加版本號.

對記錄加時間戳.

對將要更新的數據進行提前讀取、事后對比。

不論是數據庫系統本身的鎖機制,還是樂觀鎖這種業務數據級別上的鎖機制,本質上都是對狀態位的讀、寫、判斷。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容