1. 程式人生 > >一文搞懂 db2 的鎖(表鎖、行鎖、共享鎖、排他鎖)

一文搞懂 db2 的鎖(表鎖、行鎖、共享鎖、排他鎖)

鎖,很好理解,每個人都在自己的房屋上安裝有鎖,你擁有了鎖,房屋只有你能獨佔,別人不能訪問。資料庫中的鎖也一樣,只不過更加細分。

db2 中基本的鎖有兩類:

  • 排他鎖(X鎖),也叫寫鎖,當某行資料正在被修改時,其他程序不能再讀取或修改
  • 共享鎖(S鎖),也叫讀鎖,當某行資料正在被讀取時,其他程序修改

db2 事務的隔離

鎖的這種機制都是為事務隔離來服務的,這裡就不得不說下什麼是事務,事務就是資料庫管理系統執行過程一組資料庫操作,是一個邏輯單位,事務就是要保證一組資料庫操作,要麼全部成功,要麼全部失敗。可以簡單地這樣理解:事務就是一組 SQL 語句,以 begin(start) transaction 開始,以 commit 或 rollback 結束。 commit 表示提交,將事務中所有對資料庫的更新寫會到磁碟的物理資料庫中,事務正常結束。 rollback 表示回滾,即在事務執行的過程中發生了某種故障,事務不能繼續進行,系統將事務中對資料庫的所有以完成的操作全部撤消,滾回到事務開始的狀態。

那麼為什麼要進行事務隔離呢?這個問題可以反過來理解,如果不隔離,會有以下三種現象:

1、髒讀。就是事務讀取了其他事務未提交的資料,上一篇文章《一條SQL語句提交後,db2都做了什麼?》中提到,資料庫中增刪改都是在緩衝區中進行的,查詢操作也是優先去緩衝區中查詢,如果讀取了未提交時資料可能是不正確的,因為未提交的資料隨時有可能回退,一旦回退,讀取的資料肯定是無效的。

舉個例子:假如你給我轉賬 1 個億,然後我查詢自己的賬戶餘額,這裡有兩個事務:事務B:我的賬戶餘額增加 1 個億,你的賬戶餘額減少 1 個億。事務A:讀取我的賬戶餘額為 1 個億。時間點如下所示:

時間點 事務A 事務 B
0 A開始 -
1 查詢賬戶餘額為 0 B開始
2 - 轉賬給 A 1 億,A賬戶增加 1 億
3 查詢賬戶餘額為 1 億 B賬戶餘額減少 1 億
4 - B 提交

在時間點 3 處,事務 B 還未提及,事務 A 就讀取到了 1 個億,這時其實並不能認為我已經有 1 個億,因為事務 B 可能失敗而回退。

2、幻讀。就是讀取了其他事務已提交的資料,但第一次未讀取到的資料,第二次讀取到了。也可以這樣理解,一個事務第一次查詢的結果集,被其他事務插入了新行並提交給資料庫,導致第二次查詢出現了第一次查詢沒有出現的結果集,在某些情況下,這是合理的,舉例子理解如下:

時間點 事務A 事務 B
0 A開始 -
1 查詢賬戶餘額為 1 億 B開始
2 提現 1 億 轉賬給 A 1 億 並提交
3 查詢賬戶餘額仍 1 億 -

第一次查詢我的賬戶餘額為 1 億,但是取走之後第二次查詢還是 1 億,好像出現了幻覺,因此叫幻讀,原因就是第二次讀取到了事務 B 已提交的資料,提交的資料並未修改第一次的查詢結果,還是插入了新的資料。這種情況下合理的。

3、不可重複讀。與幻讀有像似之處,就是讀取了其他事務已提交的資料,事務內第一次讀取到的資料,第二次讀取不到了,也可以這樣理解,一個事務第一次查詢的結果集,被其他事務更新了,並提交給資料庫,導致第二次查詢不到了,因此叫不可重複讀,這樣會導致原先做出的決定由於條件的更改而產生偏差 ,但有時候這種情況是也合理的,舉例子如下:

時間點 事務A 事務 B
0 A開始 -
1 查詢賬戶餘額為 1 億 B開始
2 準備提現 老婆轉走賬戶 A 的 1 億,並提交
3 再查詢賬戶餘額為 0 -

由於實際應用場景非常複雜,不同的業務要求的隔離級別也不一樣,因此在進行資料庫開發時一定要考慮事務的隔離級別,否則會出大問題。

db2 有避免以上三種現場對應的隔離級別 (其他資料庫也有對應的級別,名稱可能不一樣,請注意對比 ),如下所示:

(是表示允許,否表示不允許)

隔離級別 髒讀 不可重複讀 幻讀
未提交讀(Uncommitted Read)
遊標穩定性(Cursor Stability)
讀穩定性(Read Stability)
可重複讀(Repeatable Read)

表格裡的的內容可能記不住,但是隻要理解了,就好記憶。如果還不是很理解,請看關於這 4 個隔離級別的說明:

1、未提交讀(Uncommitted Read)
db2 “select * from xxx with ur” 裡的 with ur 是什麼意思,到這你可能就明白了,ur 就是 Uncommitted Read,即未提交讀的隔離級別,允許髒讀,不加行鎖,作用就是在 select 的時候,不需要對 update 的資料進行等待。
可以在 shell 裡測試下

shell視窗1

#+c 表示不自動提交
db2 +c "insert into sometable values('value1')"

shell視窗 2

##髒讀
select * from sometable where col = 'value1' with ur"

shell視窗 3

##遊標穩定性 with cs
select * from sometable where col = 'value1' "

可以在視窗2 中看到結果,視窗 3 中是沒有結果的。

2、遊標穩定性(Cursor Stability)
db2 "select * from xxx with cs ", 這裡的 with cs 也可以不寫,因為預設的隔離級別就是這種,這種隔離級下,在一個事務中,結果集中只有正在被讀取的那一行(遊標指向的行)將被加上NS鎖(什麼是 NS 鎖,下文有),其他未被處理的行上不被加鎖。這種隔離級只能保證正在被處理的行的值不會被其他併發的程式所改變。

3、讀穩定性(Read Stability)

如果使用這種隔離級,在一個事務中所有被讀取過的行上都會被加上NS鎖,直到該事務被提交或回滾,行上的鎖才會被釋放。這樣可以保證在一個事務中即使多次讀取同一行,得到的值不會改變。但是,如果使用這種隔離級,在一個事務中,如果使用同樣的搜尋標準重新開啟已被處理過的遊標,則結果集可能改變。(可能會增加某些行,這些行被稱為幻影行(Phantom)),對應幻讀。這是因為 RS 隔離級別並不能阻止通過插入或更新操作在結果集中加入新行。

4、可重複讀(Repeatable Read)

是最嚴格的隔離級別,如果使用這種隔離級,在一個事務中所有被讀取過的行上都會被加上 S 鎖,知道該事務被提交或回滾,行上的鎖才會被釋放。這樣可以保證在一個事務中即使多次讀取同一行,得到的值不會改變。另外,在同一事務中如果以同樣的搜尋標準重新開啟已被處理過的遊標,得到的結果集不會改變。重複讀相對於讀穩定性而言,加鎖的範圍更大。

對於讀可靠性,應用程式只對符合要求的所有行加鎖,而對於重複讀,應用程式將對所有被掃描過的行都加鎖。例如,如果一個應用程式對一個表中的 10000 行資料進行掃描,最終找到了 100 條符合搜尋條件的結果行。如果該應用程式使用的是讀可靠性隔離級,應用程式將只對這符合條件的 100 行加鎖;如果該應用程式使用的是重複讀隔離級,應用程式將對被掃描過的 10000 行都加鎖。

更多關於db2 鎖的實際操練請移步 Understanding locking in DB2 Universal Database

db2 的鎖

DB2 支援對錶空間,表,行,索引(大型機裡支援對資料頁)的鎖定。通常考慮表鎖與行鎖。

加鎖策略
加表鎖:表中所有的行都受到同等程度的影響。
加行鎖:如果加鎖的範圍針對的是表及下屬的行,在在對錶加鎖後,還要在相應的資料行上加鎖。

表鎖見下表:

名稱縮寫 全名 描述
IN 無意圖鎖(Intent Node),不需要行鎖 擁有者可以讀取包括其他事務未提交資料在內的所有資料,但不能對錶中的資料作出修改
IS 意圖共享鎖(Intent Share),需要行鎖配合 擁有者可以在擁有相應行上的S鎖時可以讀取該行的資料,但不能修改資料
IX 意圖排他鎖(Intent eXclusive),需要行鎖配合 擁有者可以在擁有相應行上的X鎖時可以修改該行的資料
SIX 共享並且意圖排他鎖(Share with Intent eXclusive),需要行鎖配合 擁有者可以讀取表中的任何資料,如果在相應的行上可以獲得X鎖,可以修改該行。SIX的獲取比較特殊,當程式擁有IX鎖時請求S鎖,或者在已經擁有S鎖的時候請求IX鎖時產生
S 共享鎖(Share),不需要行鎖配合 可以讀取表上的任何資料,如果表上被加了S鎖,表上的資料只能被讀取而不能做出任何修改
U 更新鎖(Update),不需要行鎖配合 擁有者可以讀取表中的任何資料,如果升級為X鎖,則可以更改表中的任何資料,該鎖是等待對資料進行修改的一種中間狀態
X 排他鎖(eXclusive),不需要行鎖配合 擁有者可以讀取或者修改表中的任意資料,如果加上了X鎖,除了未提交讀事務外,其他程式都不能對錶進行任何讀取或者修改
Z 超級排他鎖(Super eXclusive),不需要行鎖配合 該鎖一般不是由 DML 產生,而是由Drop,Alter或者建立刪除索引時產生的,加上Z鎖後,所有程式(包括未提交讀程式)都不能對錶進行讀取或者修改

對 db2 意圖鎖的理解

牛老師的註釋:對於 IN、IX、IS 和 SIX 這些意圖鎖,我們可以這樣理解:嚴格來說他們並不是一種鎖,而是存放表中行鎖的資訊。舉個通俗的例子,我們去住一個酒店。可以把整個酒店比喻成一張表,每個房間是一個行。當我們預定一個房間時,就對該行(房間)新增 X 鎖,但是同時會在酒店的前臺對該行(房間)做一個資訊登記(旅客姓名、住多長時間等)。大家可以把意圖鎖當成這個酒店前臺,它並不是真正意義上的鎖,它維護表中每行的加鎖資訊,是共用的。後續的旅客通過酒店前臺來看哪個房間是可的,那麼,如果沒有意圖鎖,會出現什麼情況呢,假設我要住房間,那麼我每次都要到每一個房間看看這個房間有沒有住人,顯然這樣做的效率是很低下的。其實,最早的 DB2 版本是沒有意圖鎖的,但這對併發影響很大,後來就增加了意圖鎖。所有的資料庫(Oracle、Infomix 和 Sybase)都有意圖鎖的實現機制。

DB2 支援的行鎖如下所示:

名稱縮寫 全名 需要表鎖最低級別 描述
S 共享鎖(Share) IS 該行正在被讀取,其他程式只能執行讀操作
U 更改鎖(Update) IX 某個程式正在讀取並有可能修改該行,其他程式只能讀取該行
X 排他鎖(eXclusive) IX 該行正在被某個程式修改,其他程式不能訪問該行
W 弱排他鎖(Weak eXclusive) IX 一行被插入表後,該行會加上 W 鎖,只有鎖的擁有者可以修改該行,與 X 鎖的不同在於該鎖與 NW 鎖相容
NS 下一鍵共享鎖(Next Share) IS 擁有者與其他程式都可以讀取該行,但不能進行修改,當程式處於RS或者CS隔離級別下時,該鎖可以代替S鎖
NX 下一鍵排他鎖(NexteXclusive) IX 一行的資料被插入到索引或者從索引被刪除時,該行的下一行會被加上 NX 鎖,鎖的擁有者可以讀該行的資料但不能修改。該鎖與 X 鎖類似,但與 NS 鎖相容
NW 下一鍵弱排他鎖(NextWeak eXclusive) IX 一行的資料被插入到索引時,該行的下一行會被加上NW鎖,鎖的擁有者可以讀但不能修改該行的資料,與X鎖及 NX 鎖類似,但與W鎖以及 NS 鎖相容

db2 鎖轉換

當程式向資料庫請求它已經加鎖的物件上面的鎖的時候,資料庫會比較物件上現在的鎖與所請求的鎖的模式,如果所請求的鎖級別更高,則把現在的鎖升級為請求的鎖。

鎖級別比較:

  • 表鎖:IN < IS < S< IX< U< X< Z
  • 行鎖:S< U< X

有一個特殊例子是,如果持有 S 鎖請求 IX 鎖,或者持有 IX 鎖請求 S 鎖,鎖轉換結果為 SIX 鎖。

db2 鎖升級

DB2裡有兩個引數,LOCKLIST 與 MAXLOCKS:

  • LOCKLIST 表示資料庫分配的用來儲存鎖列表的空間大小
  • MAXLOCKS表示程式最大允許佔用鎖列表大小的百分比,

當超過這個百分比的時候,就會進行鎖升級,因此增大這兩個引數的值可有效避免鎖升級,但會佔用更多內在空間,比如

$ db2 get db cfg|grep -i lock
 Max storage for lock list (4KB)              (LOCKLIST) = 200000
 Percent. of lock lists per application       (MAXLOCKS) = 60
 Interval for checking deadlock (ms)         (DLCHKTIME) = 10000
 Lock timeout (sec)                        (LOCKTIMEOUT) = 120
 Block log on disk full                (BLK_LOG_DSK_FUL) = NO
 Block non logged operations            (BLOCKNONLOGGED) = NO

這裡 LOCKLIST 佔用的記憶體大小為 200000*4/1024=781.25 MB
單個應用的鎖最高可佔用 468.75MB 的記憶體。DB2 會在一個程式**鎖定過多行的時候,會把鎖定多行變更為鎖定整個表,升級為表鎖,從而降低記憶體佔用。

DLCHKTIME 預設為 10000 ms 也即 10s,是檢查死鎖的頻率,即每 10 秒檢查是否有死鎖發生。如果有列鎖,資料庫會中止發生死鎖的某個應用程式(通常為所做工作最少的那個應用程式),這會釋放這個應用程式所持有的所有的鎖,並允許別的應用程式繼續工作,DB2 將向被終止的應用程式的 SQLCA 傳送描述性的錯誤資訊。

LOCKTIMEOUT,可以設定這個引數的值來設定遇到鎖阻塞後的等待時間,如果超過這個時間,資料庫會自動回滾該事務。

(完)

公眾號 somenzz 堅持原創,和你一起學習技術。