今日分享開始啦,請大家多多指教~
當多個事務或進程訪問同一個資源時,為了保證數據的一緻性就會用到鎖機制,在MySQL中鎖有多種不同的分類。
以操作粒度區分
行級鎖、表級鎖和頁級鎖
以操作類型區分
讀鎖、寫鎖
為了允許行鎖和表鎖的共存,實現多粒度的鎖機制,InnoDB還有兩種内部使用的意向鎖,這兩種意向鎖都是表鎖:
為什麼意向鎖是表級鎖?
為了減少确認次數,提升性能:如果意向鎖是行鎖,需要遍曆每一行去确認數據是否已經加鎖;如果是表鎖的話,隻需要判斷一次就知道有沒有數據行被鎖定;
意向鎖是如何支持行級鎖、表級鎖共存的?
舉例
以操作性能區分
樂觀鎖、悲觀鎖
行鎖的實現原理
意向鎖是InnoDB自動加的,不需要用戶幹預;對于 UPDATE 、DELETE 和 INSERT 語句,InnoDB會自動給涉及的數據集增加排他鎖(X);對于普通的 SELECT 語句,InnoDB不會加任何鎖;事務也可以通過以下語句顯式的給記錄集加共享鎖
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE 和排它鎖 SELECT * FROM table_name WHERE ... FOR UPDATE 。
在InnoDB中,支持行鎖和表鎖,行鎖又分為共享鎖和排它鎖。InnoDB行鎖是通過對索引數據頁上的記錄加鎖實現的。由于InnoDB行鎖的實現特點,導緻隻有通過索引條件檢索并且執行計劃中真正使用到索引時InnoDB才會使用行鎖 ;并且不論使用主鍵索引、唯一索引、普通索引,InnoDB都會使用行鎖來進行加鎖,否則InnoDB将使用表鎖。
由于InnoDB是針對索引加鎖,而不是針對記錄加鎖,所以即使多個事務訪問不同行的記錄,但如果使用的是相同的索引,還是會出現鎖沖突的情況,甚至出現死鎖。
行鎖的不同實現
行鎖的主要實現有三種: Record Lock 、 Gap Lock 和 Next-Key Lock 。
RecordLock:記錄鎖,鎖定單個行記錄的鎖,RC和RR隔離級别支持。
GapLock:間隙鎖,鎖定索引記錄間隙,确保索引記錄的間隙不變。範圍鎖,RR隔離級别支持。(加鎖之後間隙範圍内不允許插入數據,防止發生幻讀)
Next-Key Lock:臨鍵鎖,它是記錄鎖和間隙鎖的結合體,鎖住數據的同時鎖住數據前後範圍。記錄鎖 範圍鎖,RR隔離級别支持。
insert 的加鎖流程:
執行 insert 之後,如果沒有任何沖突,在 show engine innodb status 命令中是看不到任何鎖的,這是因為 insert 加的是隐式鎖。什麼是隐式鎖?隐式鎖的意思就是沒有鎖!
所以,根本就不存在先加插入意向鎖,再加排他記錄鎖的說法,在執行 insert 語句時,什麼鎖都不會加。當其他事務執行 select ... lock in share mode 時觸發了隐式鎖的轉換。
InnoDb 在插入記錄時,是不加鎖的。如果事務 A 插入記錄且未提交,這時事務 B 嘗試對這條記錄加鎖:事務 B 會先去判斷記錄上保存的事務 id 是否活躍,如果活躍的話,那麼就幫助事務 A 去建立一個鎖對象(排他記錄鎖),然後自身進入等待事務 A 狀态,這就是所謂的隐式鎖轉換為顯式鎖。
結論:
執行 insert 語句,判斷是否有和插入意向鎖沖突的鎖,如果有,加插入意向鎖,進入鎖等待;如果沒有,直接寫數據,不加任何鎖;
執行 select ... lock in share mode 語句,判斷記錄上是否存在活躍的事務,如果存在,則為 insert 事務創建一個排他記錄鎖,并将自己加入到鎖等待隊列;
MySQL使用間隙鎖的目的間隙鎖的主要目的是為了防止幻讀,其主要通過兩個方面實現這個目的:
另外一方面,是為了滿足其恢複和複制的需要。對于基于語句的日志格式的恢複和複制而言,由于MySQL的BINLONG是按照事務提交的先後順序記錄的,因此要正确恢複或者複制數據,就必須滿足:在一個事務未提交前,其他并發事務不能插入滿足其鎖定條件的任何記錄,根本原因還是不允許出現幻讀。
鎖規則
在mySQL8.0.18及以上已經沒有這個bug
鎖結構
對不同記錄加鎖時,如果符合下邊這些條件:
那麼這些記錄的鎖就可以被放到一個鎖結構中。
鎖的兼容性
從圖中可以看出,橫向為事務A擁有的鎖,豎向為事務B想要獲取的鎖;舉例: 如果前一個事務A 持有 gap 鎖 或者 next-key 鎖的時候,後一個事務B如果想要持有 Insert Intention 鎖的時候會不兼容,出現鎖等待。
加鎖以 update t1 set name=‘XX’ where id=10 操作為例:
主鍵加鎖
加鎖行為:僅在id=10的主鍵索引記錄上加X鎖。
唯一鍵加鎖
加鎖行為:先在唯一索引id上加X鎖,然後在id=10的主鍵索引記錄上加X鎖。
非唯一鍵加鎖
加鎖行為:對滿足id=10條件的記錄和主鍵分别加X鎖,然後在(6,c)-(10,b)、(10,b)-(10,d)、(10,d)(11,f)範圍分别加Gap Lock。
無索引加鎖
加鎖行為:表裡所有行和間隙都會加X鎖。(當沒有索引時,會導緻全表鎖定,因為InnoDB引擎 鎖機制是基于索引實現的記錄鎖定)。
鎖模拟查看事務、鎖的語句:
輸出結果解析:
數據準備:
鎖舉例
鎖等待超時:ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
死鎖:1213 Deadlock found when trying to get lock
等值查詢間隙鎖
分析:
這是如果有 Session4 想要更新 id=8 的記錄,是可以執行成功的,因為間隙鎖之間互不沖突;
非唯一鍵等值鎖
分析:
LOCK IN SHARE MODE; 隻鎖覆蓋索引,FOR UPDATE; 會順便鎖上主鍵索引;
主鍵索引範圍鎖
select * from t where id=10 for update;
select * from t where id>=10 and id<11 for update;
對于以上兩條SQL,加鎖的範圍不一緻,第一條是id=10 的行鎖,第二條是 (10, 15] 的 Next-key Lock。
分析:
如果 Session3 更新一個 (10, 15) 的值,則會阻塞;
非唯一索引範圍鎖
分析:
Session1 給索引c加上了 (5,10], (10,15] 兩個 Next-key Lock ;由于是範圍查詢,不觸發優化,不會退化成間隙鎖
非唯一索引等值鎖for Update
數據準備:
在表t中,a列有普通索引,所以可能鎖定的範圍有:
(-∞, 1], (1, 3], (3, 5], (5, 8], (8, 11], (11, ∞)
Session1 執行完成之後預期加鎖範圍為 (5, 8] 和 (8, 11],由于鎖優化策略,退化成間隙鎖,範圍變成 (5, 8] 和 (8, 11) ,也就是 (5, 11) ,插入12和4不會阻塞很好理解。但是 5不在鎖的範圍内,還是被鎖上了。
是因為如果索引值相同的話,會根據id進行排序加鎖,所以最終的加鎖範圍是索引a的 (5, 4) 到 (11, 6) 的範圍。
死鎖模拟-場景1
AB BA操作問題
數據準備:
死鎖模拟-場景2
S-lock 升級 X-lock
數據準備:
沿用簡單場景1數據
分析:
死鎖模拟-場景3
數據準備:
分析:
事務一在插入時由于跟事務二插入的記錄唯一鍵沖突,所以對 a=10 這個唯一索引加 S 鎖(Next-key)并處于鎖等待,事務二再插入 a=9 這條記錄,需要獲取插入意向鎖(lock_mode X locks gap before rec insert intention)和事務一持有的 Next-key 鎖沖突,從而導緻死鎖。
死鎖模拟-場景4
死鎖日志:
并不是在日志裡看到 lock_mode X 就認為這是 Next-key 鎖,因為還有一個例外:如果在 supremum record 上加鎖,locks gap before rec 會省略掉,間隙鎖會顯示成 lock_mode X,插入意向鎖就會顯示成 lock_mode X insert intention。
INSERT 語句,會嘗試獲取lock mode S waiting 鎖,這是為了檢測唯一鍵是否重複,必須進行一次當前讀,要加 S 鎖。
INSERT 加鎖分幾個階段:先檢查唯一鍵約束,加 S 鎖,再加插入意向鎖,最後插入成功時升級為 X 鎖。
今日份分享已結束,請大家多多包涵和指點!
如何獲取?
轉發分享此文,後台私信小編:“1”即可獲取。(注:轉發分享,感謝大家)
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!