從新梳理了一下鎖、鎖與事務的關係,但願可以幫你們釐清一些知識點。本文若是不作特殊說明,默認是可重複讀隔離級別。html
在講述InnoDB鎖以前,先和你們聊一下悲觀鎖與樂觀鎖mysql
悲觀鎖和樂觀鎖闡述的是一種設計理念。程序員
悲觀鎖是不管作什麼都須要先獲取到鎖,樂觀鎖其實並無鎖的概念,作任何操做都不加鎖,可是更新數據的時候會檢查要更新的數據是否被修改過,通常用CAS實現(Compare-and-Set)算法
悲觀鎖:先取鎖再訪問。數據庫中的行鎖,表鎖,讀鎖(共享鎖),寫鎖(排他鎖)均爲悲觀鎖sql
樂觀鎖:不會上鎖,可是若是想要更新數據,則會在更新前檢查在讀取至更新這段時間別人有沒有修改過這個數據。若是修改過,則從新讀取,再次嘗試更新,循環上述步驟直到更新成功數據庫
悲觀鎖相對影響性能,樂觀鎖由於不加鎖,性能會更好,你們能夠根據具體狀況選擇不一樣的設計。緩存
如今讓咱們聊一下InnoDB的鎖,InnoDB支持兩種級別的鎖session
行級別鎖:共享鎖(S)和排它鎖(X)併發
表級別鎖:意向共享鎖(IS)和意向排它鎖(IX)。框架
1)意向共享鎖(IS鎖):事務在請求S鎖前,要先得到IS鎖 2)意向排他鎖(IX鎖):事務在請求X鎖前,要先得到IX鎖
由於InnoDB存儲引擎支持的是行級別的鎖,因此意向鎖其實不會阻塞除全表掃描之外的任何請求。另外意向共享鎖(IS)和意向排它鎖(IX)是由InnoDB自行作加鎖和解鎖操做的,因此本文主要講一下行級別鎖。
共享鎖:
for update
)排它鎖:
二者的兼容性以下圖所示:
添加共享鎖: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寫的很差。
幻讀:
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種行鎖的算法設計,分別是:
在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)。
鎖和事務的關係:事務的隔離性經過鎖來實現。
爲何鎖能實現隔離性,由於加了鎖以後,數據就不能被別人隨便更改了。
查看是否自動提交 show session variables like 'autocommit';
查詢正在執行的事務 SELECT * FROM information_schema.INNODB_TRX;
查看正在鎖的事務
查看等待鎖的事務
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
SELECT * FROM sys.innodb_lock_waits; - mysql8.0
查看mysql當前默認的存儲引擎 show variables like '%storage_engine%';
查看mysql版本 select version();
查看隔離級別 select @@transaction_isolation;
你們若是喜歡個人文章,能夠關注個人公衆號(程序員麻辣燙)
往期文章回顧: