MySQL InnoDB鎖機制全面解析分享

寫在前面:在設計新零售供應鏈wms(倉庫管理系統)庫存模塊時,爲了防止併發狀況對庫存的影響,查閱了一些資料,對InnoDB鎖機制有了更全面的瞭解,在此作出分享,若有疏漏望不吝指正,願共同進步!(此篇爲1.0版本,後續隨理解深刻,會逐步迭代完善~)算法

1、爲何要加鎖

鎖機制用於管理對共享資源的併發訪問。

當多個用戶併發地存取數據時,在數據庫中就可能會產生多個事務同時操做同一行數據的狀況,若對併發操做不加控制就可能會讀取和存儲不正確的數據,破壞數據的一致性。sql

一種典型的併發問題——丟失更新(其餘鎖問題及解決方法會在後面說到):數據庫

注:RR默認隔離級別下,爲更清晰體現時間前後,暫時忽略鎖等待,不影響最終效果~
時間點 事務A 事務B
1 開啓事務A
2 開啓事務B
3 查詢當前商品S庫存爲100
4 查詢當前商品S庫存爲100
5 業務邏輯處理,肯定要將商品S庫存增長10,故更新庫存爲110(update stock set amount=110 where sku_id=S;)
6 業務邏輯處理,肯定要將商品S庫存增長20,故更新庫存爲120(update stock set amount=120 where sku_id=S;)
7 提交事務A
8 提交事務B

異常結果:商品S庫存更新爲120,但實際上針對商品S進行了兩次入庫操做,最終商品S庫存應爲100+10+20=130,但實際結果爲120,首先提交的事務A的更新『丟失了』!!!因此就須要鎖機制來保證這種狀況不會發生。segmentfault

2、InnoDB鎖類型概述

這裏寫圖片描述

簡介(後面會分別詳細說到):網絡

一、樂觀鎖與悲觀鎖是兩種併發控制的思想,可用於解決丟失更新問題:

樂觀鎖會「樂觀地」假定大機率不會發生併發更新衝突,訪問、處理數據過程當中不加鎖,只在更新數據時再根據版本號或時間戳判斷是否有衝突,有則處理,無則提交事務;併發

悲觀鎖會「悲觀地」假定大機率會發生併發更新衝突,訪問、處理數據前就加排他鎖,在整個數據處理過程當中鎖定數據,事務提交或回滾後才釋放鎖;性能

二、InnoDB支持多種鎖粒度,默認使用行鎖,鎖粒度最小,鎖衝突發生的機率最低,支持的併發度也最高,但系統消耗成本也相對較高;
三、共享鎖與排他鎖是InnoDB實現的兩種標準的行鎖;
四、InnoDB有三種鎖算法——記錄鎖、gap間隙鎖、還有結合了記錄鎖與間隙鎖的next-key鎖,InnoDB對於行的查詢加鎖是使用的是next-key locking這種算法,必定程度上解決了幻讀問題;
五、意向鎖是爲了支持多種粒度鎖同時存在;(1.0版本不重點介紹,若有興趣可參看知乎推薦回答https://www.zhihu.com/questio...spa

3、行鎖詳解

InnoDB默認使用行鎖,實現了兩種標準的行鎖——共享鎖與排他鎖;
這裏寫圖片描述設計

注意:
一、除了顯式加鎖的狀況,其餘狀況下的加鎖與解鎖都無需人工干預。
二、InnoDB全部的行鎖算法都是基於索引實現的,鎖定的也都是索引或索引區間(這一點會在第六章節『鎖算法』中詳細說到);3d

共享鎖與排它鎖兼容性示例(使用默認的RR隔離級別,圖中數字從小到大標識操做執行前後順序):

這裏寫圖片描述

4、當前讀與快照讀

一、當前讀:即加鎖讀,讀取記錄的最新版本,會加鎖保證其餘併發事務不能修改當前記錄,直至獲取鎖的事務釋放鎖;

使用當前讀的操做主要包括:顯式加鎖的讀操做與插入/更新/刪除等寫操做,以下所示:

select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values (…);
update table set ? where ?;
delete from table where ?;
注:當Update SQL被髮給MySQL後,MySQL Server會根據where條件,讀取第一條知足條件的記錄,而後InnoDB引擎會將第一條記錄返回,並加鎖,待MySQL Server收到這條加鎖的記錄以後,會再發起一個Update請求,更新這條記錄。一條記錄操做完成,再讀取下一條記錄,直至沒有知足條件的記錄爲止。所以,Update操做內部,就包含了當前讀。同理,Delete操做也同樣。Insert操做會稍微有些不一樣,簡單來講,就是Insert操做可能會觸發Unique Key的衝突檢查,也會進行一個當前讀。

二、快照讀:即不加鎖讀,讀取記錄的快照版本而非最新版本,經過MVCC實現;

InnoDB默認的RR事務隔離級別下,不顯式加『lock in share mode』與『for update』的『select』操做都屬於快照讀,保證事務執行過程當中只有第一次讀以前提交的修改和本身的修改可見,其餘的均不可見;

5、MVCC

MVCC『多版本併發控制』,與之對應的是『基於鎖的併發控制』;

MVCC的最大好處:讀不加任何鎖,讀寫不衝突,對於讀操做多於寫操做的應用,極大的增長了系統的併發性能;

InnoDB默認的RR事務隔離級別下,不顯式加『lock in share mode』與『for update』的『select』操做都屬於快照讀,使用MVCC,保證事務執行過程當中只有第一次讀以前提交的修改和本身的修改可見,其餘的均不可見;

關於InnoDB MVCC的實現原理,在《高性能Mysql》一書中有一些說明,網絡上也大多沿用這一套理論,但這套理論與InnoDB的實際實現仍是有必定差距的,但不妨咱們經過它初步理解MVCC的實現機制,因此我在此貼上此書中的說明;

這裏寫圖片描述

6、鎖算法

InnoDB主要實現了三種行鎖算法:
這裏寫圖片描述

InnoDB全部的行鎖算法都是基於索引實現的,鎖定的也都是索引或索引區間;

不一樣的事務隔離級別、不一樣的索引類型、是否爲等值查詢,使用的行鎖算法也會有所不一樣;下面僅以InnoDB默認的RR隔離級別、等值查詢爲例,介紹幾種行鎖算法:
這裏寫圖片描述

一、等值查詢使用聚簇索引
這裏寫圖片描述

注: InnoDB表是索引組織表,根據主鍵索引構造一棵B+樹,葉子節點存放的是整張表的行記錄數據,且按主鍵順序存放;我這裏作了一個表格模擬主鍵索引的葉子節點,使用主鍵索引查詢,就會鎖住相關主鍵索引,鎖住了索引也就鎖住了行記錄,其餘併發事務就沒法修改此行數據,直至提交事務釋放鎖,保證了併發狀況下數據的一致性;

二、等值查詢使用惟一索引
這裏寫圖片描述

注:輔助索引的葉子節點除了存放輔助索引值,也存放了對應主鍵索引值;鎖定時會鎖定輔助索引與主鍵索引;

三、等值查詢使用輔助索引
這裏寫圖片描述

注:Gap鎖,鎖定的是索引記錄之間的間隙,是防止幻讀的關鍵;若是沒有上圖中綠色標識的Gap Lock,其餘併發事務在間隙中插入了一條記錄如:『insert into stock (id,sku_id) values(2,103);』並提交,那麼在此事務中重複執行上圖中SQL,就會查詢出併發事務新插入的記錄,即出現幻讀;(幻讀是指在同一事務下,連續執行兩次一樣的SQL語句可能致使不一樣的結果,第二次的SQL語句可能返回以前不存在的行記錄)加上Gap Lock後,併發事務插入新數據前會先檢測間隙中是否已被加鎖,防止幻讀的出現;

更多鎖示例可參看博客:https://yq.aliyun.com/article...
更多鎖算法詳解可參看何博士博客:http://hedengcheng.com/?p=771

7、鎖問題

MySQL鎖會帶來以下幾種問題,若是能解決他們,就能夠保證併發狀況下不會出現問題;

鎖問題 鎖問題描述 會出現鎖問題的隔離級別 解決辦法
髒讀 一個事務中會讀到其餘併發事務未提交的數據,違反了事務的隔離性; Read Uncommitted 提升事務隔離級別至Read Committed及以上;
不可重複讀 一個事務會讀到其餘併發事務已提交的數據,違反了數據庫的一致性要求;可能出現的問題爲幻讀,幻讀是指在同一事務下,連續執行兩次一樣的SQL語句可能致使不一樣的結果,第二次的SQL語句可能返回以前不存在的行記錄; Read Uncommitted、Read Committed 默認的RR隔離級別下 ,解決辦法分爲兩種狀況:一、當前讀:Next-Key Lock機制對相關索引記錄及索引間隙加鎖,防止併發事務修改數據或插入新數據到間隙;(詳情參見第六章節『鎖算法』)二、版本讀:MVCC,保證事務執行過程當中只有第一次讀以前提交的修改和本身的修改可見,其餘的均不可見;提升事務隔離級別至Serializable;
丟失更新 見章節一中描述; Read Uncommitted、Read Committed、Repeatable Read 默認的RR隔離級別下 ,解決辦法分爲兩種狀況:一、樂觀鎖:數據表增長version字段,讀取數據時記錄原始version,更新數據時,比對version是否爲原始version,如不等,則證實有併發事務已更新過此行數據,則可回滾事務後重試直至無併發競爭;二、悲觀鎖:讀加排他鎖,保證整個事務執行過程當中,其餘併發事務沒法讀取相關記錄,直至當前事務提交或回滾釋放鎖;詳情可參看博客:https://segmentfault.com/a/11...
注:其實InnoDB默認的RR事務隔離級別已經爲咱們作了大多數的事,業務中更多須要關心『丟失更新』這種問題,一般使用樂觀鎖方式解決;咱們在讀操做時通常不會使用加鎖讀,但MVCC並不能徹底解讀幻讀問題,其餘併發事務是能夠插入符合當前事務查詢條件的數據,只是當前事務由於讀快照數據沒法查看到,這種狀況下應該使用惟一索引等方式保證不會重複插入重複的業務數據,在此再也不贅述~

參考及推薦

書籍:《高性能MySQL》
書籍:《MySQL技術內幕:InnoDB存儲引擎》(強烈推薦)
鎖算法示例:https://yq.aliyun.com/article...
MySQL 加鎖處理分析:http://hedengcheng.com/?p=771
知乎MySQL鎖總結:https://zhuanlan.zhihu.com/p/...
知乎InnoDB意向鎖討論:https://www.zhihu.com/questio...
MySQL使用鎖解決併發下的更新丟失問題:https://segmentfault.com/a/11...

相關文章
相關標籤/搜索