MySQL鎖係列(一)之鎖的種類和概念
背景
鎖是MySQL裏麵最難理解的知識,但是又無處不在。
一開始接觸鎖的時候,感覺被各種鎖類型和名詞弄得暈頭轉向,就別說其他了。
本文是通過DBA的視角(非InnoDB內核開發)來分析和窺探鎖的奧秘,並解決實際工作當中遇到的問題
鎖的種類&概念
想要啃掉這塊最難的大骨頭,必須先畫一個框架,先了解其全貌,才能逐個擊破
- Shared and Exclusive Locks
* Shared lock: 共享鎖,官方描述:permits the transaction that holds the lock to read a row
eg:select * from xx where a=1 lock in share mode
* Exclusive Locks:排他鎖: permits the transaction that holds the lock to update or delete a row
eg: select * from xx where a=1 for update
- Intention Locks
1. 這個鎖是加在table上的,表示要對下一個層級(記錄)進行加鎖
2. Intention shared (IS):Transaction T intends to set S locks on individual rows in table t
3. Intention exclusive (IX): Transaction T intends to set X locks on those rows
4. 在數據庫層看到的結果是這樣的:
TABLE LOCK table `lc_3`.`a` trx id 133588125 lock mode IX
- Record Locks
1. 在數據庫層看到的結果是這樣的:
RECORD LOCKS space id 281 page no 3 n bits 72 index PRIMARY of table `lc_3`.`a` trx id 133588125 lock_mode X locks rec but not gap
2. 該鎖是加在索引上的(從上麵的index PRIMARY of table `lc_3`.`a` 就能看出來)
3. 記錄鎖可以有兩種類型:lock_mode X locks rec but not gap && lock_mode S locks rec but not gap
- Gap Locks
1. 在數據庫層看到的結果是這樣的:
RECORD LOCKS space id 281 page no 5 n bits 72 index idx_c of table `lc_3`.`a` trx id 133588125 lock_mode X locks gap before rec
2. Gap鎖是用來防止insert的
3. Gap鎖,中文名間隙鎖,鎖住的不是記錄,而是範圍,比如:(negative infinity, 10),(10, 11)區間,這裏都是開區間哦
- Next-Key Locks
1. 在數據庫層看到的結果是這樣的:
RECORD LOCKS space id 281 page no 5 n bits 72 index idx_c of table `lc_3`.`a` trx id 133588125 lock_mode X
2. Next-Key Locks = Gap Locks + Record Locks 的結合, 不僅僅鎖住記錄,還會鎖住間隙,比如: (negative infinity, 10】,(10, 11】區間,這些右邊都是閉區間哦
- Insert Intention Locks
1. 在數據庫層看到的結果是這樣的:
RECORD LOCKS space id 279 page no 3 n bits 72 index PRIMARY of table `lc_3`.`t1` trx id 133587907 lock_mode X insert intention waiting
2. Insert Intention Locks 可以理解為特殊的Gap鎖的一種,用以提升並發寫入的性能
- AUTO-INC Locks
1. 在數據庫層看到的結果是這樣的:
TABLE LOCK table xx trx id 7498948 lock mode AUTO-INC waiting
2. 屬於表級別的鎖
3. 自增鎖的詳細情況可以之前的一篇文章:
https://keithlan.github.io/2017/03/03/auto_increment_lock/
- 顯示鎖 vs 隱示鎖
* 顯示鎖(explicit lock)
顯示的加鎖,在show engine innoDB status 中能夠看到 ,會在內存中產生對象,占用內存
eg: select ... for update , select ... lock in share mode
* 隱示鎖(implicit lock)
implicit lock 是在索引中對記錄邏輯的加鎖,但是實際上不產生鎖對象,不占用內存空間
* 哪些語句會產生implicit lock 呢?
eg: insert into xx values(xx)
eg: update xx set t=t+1 where id = 1 ; 會對輔助索引加implicit lock
* implicit lock 在什麼情況下會轉換成 explicit lock
eg: 隻有implicit lock 產生衝突的時候,會自動轉換成explicit lock,這樣做的好處就是降低鎖的開銷
eg: 比如:我插入了一條記錄10,本身這個記錄加上implicit lock,如果這時候有人再去更新這條10的記錄,那麼就會自動轉換成explicit lock
* 數據庫怎麼知道implicit lock的存在呢?如何實現鎖的轉化呢?
1. 對於聚集索引上麵的記錄,有db_trx_id,如果該事務id在活躍事務列表中,那麼說明還沒有提交,那麼implicit則存在
2. 對於非聚集索引:由於上麵沒有事務id,那麼可以通過上麵的主鍵id,再通過主鍵id上麵的事務id來判斷,不過算法要非常複雜,這裏不做介紹
- metadata lock
1. 這是Server 層實現的鎖,跟引擎層無關
2. 當你執行select的時候,如果這時候有ddl語句,那麼ddl會被阻塞,因為select語句擁有metadata lock,防止元數據被改掉
- 鎖遷移
1. 鎖遷移,又名鎖繼承
2. 什麼是鎖遷移呢?
a) 滿足的場景條件:
b)我鎖住的記錄是一條已經被標記為刪除的記錄,但是還沒有被puge
c) 然後這條被標記為刪除的記錄,被purge掉了
d) 那麼上麵的鎖自然而然就繼承給了下一條記錄,我們稱之為鎖遷移
- 鎖升級
鎖升級指的是:一條全表更新的語句,那麼數據庫就會對所有記錄進行加鎖,那麼可能造成鎖開銷非常大,可能升級為頁鎖,或者表鎖。
MySQL 沒有鎖升級
- 鎖分裂
1. InnoDB的實現加鎖,其實是在頁上麵做的,沒有辦法直接對記錄加鎖
2. 一個頁被讀取到內存,然後會產生鎖對象,鎖對象裏麵會有位圖信息來表示哪些heapno被鎖住,heapno表示的就是堆的序列號,可以認為就是定位到某一條記錄
3. 大家又知道,由於B+tree的存在,當insert的時候,會產生頁的分裂動作
4. 如果頁分裂了,那麼原來對頁上麵的加鎖位圖信息也就變了,為了保持這種變化和鎖信息,鎖對象也會分裂,由於繼續維護分裂後頁的鎖信息
- 鎖合並
鎖的合並,和鎖的分裂,其實原理是一樣的,參考上麵即可。
至於鎖合並和鎖分裂的算法,比較複雜,這裏就不介紹了
- latch vs lock
* latch
mutex
rw-lock
臨界資源用完釋放
不支持死鎖檢測
以上是應用程序中的鎖,不是數據庫的鎖
* lock
當事務結束後,釋放
支持死鎖檢測
數據庫中的鎖
鎖的兼容矩陣
- X vs S
兼容性 | X | S |
---|---|---|
X | N | N |
S | N | Y |
- IS,IX,S,X
兼容性 | IS | IX | S | X |
---|---|---|---|---|
IS | Y | Y | Y | N |
IX | Y | Y | N | N |
S | Y | N | Y | N |
X | N | N | N | N |
- AI,IS,IX,S,X
兼容性 | AI | IS | IX | S | X |
---|---|---|---|---|---|
AI | N | Y | Y | N | N |
IS | Y | Y | Y | Y | N |
IX | Y | Y | Y | N | N |
S | N | Y | N | Y | N |
X | N | N | N | N | N |
參考資料
1. https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html
2. MySQL技術內幕:InnoDB 存儲引擎
3. MySQL內核:InnoDB 存儲引擎
最後更新:2017-06-05 16:31:56