InnoDB鎖與事務簡析

從新梳理了一下鎖、鎖與事務的關係,但願可以幫你們釐清一些知識點。本文若是不作特殊說明,默認是可重複讀隔離級別。html

悲觀鎖與樂觀鎖

在講述InnoDB鎖以前,先和你們聊一下悲觀鎖與樂觀鎖mysql

悲觀鎖和樂觀鎖闡述的是一種設計理念。程序員

悲觀鎖是不管作什麼都須要先獲取到鎖,樂觀鎖其實並無鎖的概念,作任何操做都不加鎖,可是更新數據的時候會檢查要更新的數據是否被修改過,通常用CAS實現(Compare-and-Set)算法

悲觀鎖先取鎖再訪問。數據庫中的行鎖,表鎖,讀鎖(共享鎖),寫鎖(排他鎖)均爲悲觀鎖sql

樂觀鎖不會上鎖,可是若是想要更新數據,則會在更新前檢查在讀取至更新這段時間別人有沒有修改過這個數據。若是修改過,則從新讀取,再次嘗試更新,循環上述步驟直到更新成功數據庫

悲觀鎖相對影響性能,樂觀鎖由於不加鎖,性能會更好,你們能夠根據具體狀況選擇不一樣的設計。緩存

InnoDB鎖

如今讓咱們聊一下InnoDB的鎖,InnoDB支持兩種級別的鎖session

行級別鎖:共享鎖(S)和排它鎖(X)併發

表級別鎖:意向共享鎖(IS)和意向排它鎖(IX)。框架

1)意向共享鎖(IS鎖):事務在請求S鎖前,要先得到IS鎖 2)意向排他鎖(IX鎖):事務在請求X鎖前,要先得到IX鎖

由於InnoDB存儲引擎支持的是行級別的鎖,因此意向鎖其實不會阻塞除全表掃描之外的任何請求。另外意向共享鎖(IS)和意向排它鎖(IX)是由InnoDB自行作加鎖和解鎖操做的,因此本文主要講一下行級別鎖。

共享鎖與排它鎖

共享鎖和排他鎖的特性

共享鎖

  • 容許其它事務也增長共享鎖讀取
  • 不容許其它事物增長排他鎖 (for update)
  • 當事務同時增長共享鎖時候,事務的更新必須等待先執行的事務 commit 後才行,若是同時併發太大可能很容易形成死鎖

排它鎖

  • 事務之間不容許其它排他鎖或共享鎖讀取,修改更不可能
  • 一次只能有一個排他鎖執行 commit 以後,其它事務纔可執行

二者的兼容性以下圖所示:

在InnoDB中如何加共享鎖或者排它鎖?

添加共享鎖:SELECT...LOCK IN SHARE MODE,如select * from test1 where id = 1 lock in share mode;

添加排它鎖:SELECT...FOR UPDATE,如 select * from test1 where name = 5 for update;

若是不使用lock in share mode或者for update,僅使用select,是不會加鎖的。此時極易產生丟失更新或者幻讀的狀況。

演示

表結構以下:

CREATE TABLE test1 ( id int unsigned NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;

現有數據以下:

先看一下不加鎖的狀況下會出現的一些問題

丟失更新:

Session1 Session2
1 start transaction; start transaction;
2 select name into @name from test1 where id =1; select name into @name from test1 where id =1;
3 update test1 set name = @name - 100;
4 commit;
5 update test1 set name = @name + 100;
6 commit;

這種狀況下,session1作的更改被session2覆蓋了,最終的數值會變爲188。固然這種狀況發生的主要緣由是sql寫的很差。

  • update的時候,使用update test1 set name = name - 100(沒有@),就能夠避免這個問題。不要先查詢,而後用查詢到的值來更新
  • sql若是寫的嚴謹,對於mysql的任何隔離級別來講,都不會發生丟失更新的問題,由於mysql會對DML操做加鎖,兩個事務更新同一條數據的時候,後來的更新會被阻塞住。

幻讀:

Session1 Session2
1 start transaction; start transaction;
2 select * from test1 where name =1;(顯示2 5 6 三條)
3 update test1 set name = 2 where id=2;
4 select * from test1 where name =1;(顯示2 5 6 三條)
5 commit;
6 select * from test1 where name =1;(顯示2 5 6 三條)
7 update test1 set name = 3 where name=1;(隻影響兩行)
8 commit;

能夠看出,儘管session2更新了數據,可是session1查詢的時候,數據仍然沒有變化,可是更新的時候,只更改了兩行,這就出現了幻象

  • session1讀取的時候一直爲三條,是由於mysql的select使用一致性的非鎖定讀操做,這個操做是經過多版本併發控制(Multi Version Concur-rency Control,MVCC)實現的。簡單來講select讀取的是個快照

    • 可重複讀隔離級別下,讀取的快照是事務啓動前的快照,因此不管別的事務怎麼更改數據,當前事務讀取的數據是不變的

    • 讀已提交隔離級別下,讀取的的快照是最新的數據快照,因此別的事務提交後,當前事務讀取會讀取到最新的值

  • 更新的時候由於須要真正的修改數據,此時發現有一條不符合,因此只更新了兩條

爲了寫代碼的時候沒有bug,咱們能夠加鎖,將要變動的資源鎖住,這樣只有本事務能夠對數據作操做,不怕其餘事務對數據作update delete insert等操做,這裏簡單寫一個排它鎖

排它鎖

Session1 Session2
1 start transaction; start transaction;
2 select * from test1 where name =1 for update;(顯示2 5 6 三條)
3 update test1 set name = 2 where id=2; (阻塞)
4 select * from test1 where name =1;(顯示2 5 6 三條)
5 update test1 set name = 3 where name=1;(影響三行)
6 commit;
7 update得以執行
8 commit;

能夠看出,該示例和幻讀的示例相比,只是session1在select時添加了for update(排它鎖),經過這個操做便鎖住了資源,session2的update沒法執行。相信到這裏你們對mysql的鎖的做用有了比較清晰的理解。

鎖的算法

InnoDB存儲引擎有3種行鎖的算法設計,分別是:

  • Record Lock:單個行記錄上的鎖。
  • Gap Lock:間隙鎖,鎖定一個範圍,但不包含記錄自己。
  • Next-Key Lock:Gap Lock+Record Lock,鎖定一個範圍,而且鎖定記錄自己。

在REPEATABLE READ模式下,Next-Key Lock算法是默認的行記錄鎖定算法。可是InnoDB存儲引擎會根據狀況本身選一個最小的算法模型,即Next-Key Lock會退化成Record Lock或者Gap Lock。

Record Lock比較好理解,就是對單行加鎖,只鎖定一行,如通常where = 的時候會使用行鎖。Gap Lock和Next Key Lock鎖定一個範圍,通常where < 的時候會鎖定範圍,若是我使用select * from test1 where id <100 for update;,那麼其餘事務不管是insert或者update id<100的記錄都會被阻塞,可是100以外的沒有問題。因此Mysql在REPEATABLE READ模式下經過Record Lock解決了幻讀問題。

錯誤用鎖致使的問題

鎖若是使用錯誤,會致使一些問題產生,如死鎖或者不當心將整個表鎖住。

死鎖

Session1 Session2
1 start transaction; start transaction;
2 select * from test1 where id =1 for update;
3 select * from test1 where id =2 for update;
4 select * from test1 where id =1 for update;
5 select * from test1 where id =2 for update;(ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction)

死鎖發生狀況不少,上面只展現了其中一種,mysql有解除死鎖的機制:發現死鎖後,InnoDB存儲引擎會立刻回滾一個事務。但你們儘可能不要寫出有死鎖的代碼。

鎖住整張表

使用select…for update會把數據給鎖住,不過咱們須要注意一些鎖的級別,MySQL InnoDB默認Row-Level Lock,因此只有「明確」地指定主鍵,MySQL 纔會執行Row lock (只鎖住被選取的數據) ,不然MySQL 將會執行Table Lock (將整個數據表單給鎖住)。

只有經過索引條件檢索數據,InnoDB纔會使用行級鎖,不然,InnoDB將使用表鎖!

你們能夠用select * from performance_schema.data_locks;查看被鎖住的數據。

鎖與事務的關係

上面講述了鎖的不少信息,那麼鎖與事務有什麼關係呢?

你們都知道到Mysql的事務有四個特性,即ACID,原子性(Atomicity)、一致性(Correspondence)、隔離 性(Isolation)、持久性(Durability)。

鎖和事務的關係:事務的隔離性經過鎖來實現。

爲何鎖能實現隔離性,由於加了鎖以後,數據就不能被別人隨便更改了。

經常使用命令

  1. 查看是否自動提交 show session variables like 'autocommit';

  2. 查詢正在執行的事務 SELECT * FROM information_schema.INNODB_TRX;

  3. 查看正在鎖的事務

    • SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
    • select * from performance_schema.data_locks; - mysql8.0
  4. 查看等待鎖的事務

    • SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

    • SELECT * FROM sys.innodb_lock_waits; - mysql8.0

  5. 查看mysql當前默認的存儲引擎 show variables like '%storage_engine%';

  6. 查看mysql版本 select version();

  7. 查看隔離級別 select @@transaction_isolation;

參考資料

  1. 悲觀鎖與樂觀鎖的實現(詳情圖解)
  2. MySQL的SELECT ...for update
  3. MySQL 共享鎖 (lock in share mode),排他鎖 (for update)
  4. MySQL的自動提交模式
  5. mac 安裝mysql@5.7 (brew 安裝配置)
  6. MySQL 函數
  7. mysql8.0查看鎖信息
  8. www.jianshu.com/p/32904ee07… 間歇鎖
  9. Mysql加鎖過程詳解(9)-innodb下的記錄鎖,間隙鎖,next-key鎖
  10. mysql的共享鎖(S)、排他鎖(X)、意向共享鎖(IS)、意向排他鎖(IX)的關係
  11. Mysql-丟失更新
  12. MySQL技術內幕:InnoDB存儲引擎

最後

你們若是喜歡個人文章,能夠關注個人公衆號(程序員麻辣燙)

往期文章回顧:

  1. CDN請求過程詳解
  2. 關於程序員職業發展的思考
  3. 記博客服務被壓垮的歷程
  4. 經常使用緩存技巧
  5. 如何高效對接第三方支付
  6. Gin框架簡潔版
  7. 關於代碼review的思考
  8. InnoDB鎖與事務簡析
  9. Markdown編輯器推薦-typora
相關文章
相關標籤/搜索