MySQL事務和鎖

IT王小二發表於2022-03-16

作者:IT王小二

部落格:https://itwxe.com

MySQL是怎麼解決併發事務所產生的問題呢?又藉助了哪些鎖的思想呢?這篇小二給小夥伴們繼續嘮一嘮MySQL的那些事。

一、事務是什麼

事務是由一組SQL語句組成的邏輯處理單元,這些操作要麼全部執行,要麼全部不執行,是一個不可分割的工作單位。通常有下面4個特性,也就是我們程式猿俗稱的ACID屬性。

  • 原子性(Atomicity):事務是一個原子操作單元,其對資料的修改,要麼全部執行,要麼全部不執行。舉個例子:情人節小二給女朋友轉賬520塊,那麼SQL中一共兩個操作,小二的賬戶餘額-520塊,女朋友賬戶餘額+520塊,這一組轉賬操作,不能只執行小二的賬戶-520塊,或者只執行女朋友賬戶餘額+520塊,只能都執行,或者都不執行。
  • 一致性(Consistent):在事務開始和完成時,資料都必須保持一致狀態。一致性和原子性息息相關,即一組轉賬操作下來小二賬戶餘額應該-520塊,女朋友賬戶餘額+520塊,兩人轉賬操作之後賬戶總金額是一致的。
  • 隔離性(Isolation):一個事務的執行不能被其他事務干擾,即每個事務都是獨立的,不受其他事務的影響。簡單來說就是一個事務(T1)處理過程的中間狀態對其他事務(T2, T3...)是不可見的;同理,T2同樣對T1和T3的中間狀態不可見的。即只要小二的轉賬操作沒完成,那麼女朋友不管怎麼查詢結果都是原來的餘額,而不會查詢多出520塊。
  • 永續性(Durable):事務完成之後,對資料的修改是永久性的,即便是在資料庫系統遇到故障的情況下也不會丟失提交事務的操作。即只要小二轉賬成功了,資料就儲存磁碟了。

二、併發事務所產生的問題

髒寫或者更新丟失(Lost Update)

當兩個或多個事務同一時間修改同一行,然後基於最初選定的值進行業務操作更新該行時,由於每個事務都不知道其他事務的存在,就會發生丟失更新問題。

簡單來說就是最後的更新覆蓋了由其他事務所做的更新

髒讀(Dirty Reads)

一個事務讀到了其他事務已經修改但是未提交的資料,未提交意味著這些資料可能會回滾,也就是可能最終不一定會存到資料庫中,所以讀出來的資料是無效的。

不可重複讀(Non-Repeatable Reads)

一個事務在讀取某些資料後的某個時間點,再次讀取以前讀過的那批資料,卻發現其讀出的資料已經發生了改變,可能會受到其他事務的影響,比如其他事務改了這批資料並提交了。通常針對資料更新(UPDATE)操作或者刪除(DELETE)操作。

簡單來說就是在一個事務中,相同查詢語句在不同時刻查詢出來的結果不一致,可能是結果欄位值不一致,也可能是少了行資料

幻讀(Phantom Reads)

對比不可重複讀,幻讀是針對資料新增(INSERT)來說的,在同一事務下,在讀取某些資料後的某個時間點,再次讀取以前讀過的那批資料,第二次的SQL語句返回了之前不存在的行

簡單來說就是事務A讀取到了其他事務提交的新增資料

再來區分一下經常搞混的不可重複讀幻讀

  • 不可重複讀:說的是原來存在的記錄A,記錄A從A變成了記錄B。
  • 幻讀:出現了原來不存在的記錄。

當然還是區分不明顯的小夥伴可以結合五、行鎖與事務隔離級別案例分析理解理解,理論結合實踐,nice~

三、事務隔離級別

併發事務產生的髒讀、不可重複讀、幻讀都是資料庫讀取的一致性,問題,所以資料庫提供了一定的事務隔離級別來解決產生的問題。

隔離級別 髒讀 不可重複讀 幻讀
讀未提交(read-uncommitted)
不可重複讀(read-committed)
可重複讀(repeatable-read)
序列化(serializable)

MySQL資料庫預設的隔離級別為可重複讀(repeatable-read),也就是我們常說的RR級別。

檢視事務隔離級別:show variables like 'tx_isolation';

設定事務隔離級別,例如設定隔離級別為讀未提交:

  • 僅對當前會話生效,立即生效:set session transaction isolation level read uncommitted;或者set tx_isolation = 'read-uncommitted';
  • 對全域性會話生效,需要退出會話後生效:set global transaction isolation level read uncommitted;

用Spring開發程式時,如果不設定隔離級別預設用MySQL設定的隔離級別,如果Spring設定了就用設定的隔離級別。

四、鎖分類

鎖是計算機協調多個程式或者執行緒併發訪問同一資源的機制,而對於資料庫來說資料就是一種需要使用者共享的資源,怎麼保證資料的併發訪問一致性和高效性是資料庫需要解決的問題。

按不同分類有以下分類。

  • 從效能上來分,分為樂觀鎖和悲觀鎖
    • 樂觀鎖比較樂觀,樂觀鎖假設資料一般情況不會造成衝突,多執行緒同時對同一行資料修改的時候,在資料進行提交更新的時候,才會正式對資料的衝突與否進行檢測,如果衝突,則返回給使用者異常資訊,讓使用者決定如何去做。
    • 悲觀鎖比較悲觀,多執行緒同時對同一行資料修改的時候,最終只有一個執行緒修改成功。
  • 從對資料庫操作的型別分,分為讀鎖和寫鎖(都屬於悲觀鎖)。
    • 讀鎖(共享鎖,S鎖(Shared)):針對同一份資料,多個讀操作可以同時進行而不會互相影響。
    • 寫鎖(排它鎖,X鎖(eXclusive)):當前寫操作沒有完成前,它會阻斷其他寫鎖和讀鎖。
  • 從對資料操作的粒度分,分為表鎖和行鎖

1. 表鎖

每次操作鎖住整張表。開銷小,加鎖快,不會出現死鎖,鎖定粒度大,發生鎖衝突的概率最高,併發度最低,一般用在整表資料遷移的場景。

基本操作

  • 手動增加表鎖:lock table 表名稱 read/write,表名稱2 read/write;
  • 檢視錶上加過的鎖:show open tables;
  • 刪除表鎖:unlock tables;

實操一下

-- 建立示例表
CREATE TABLE `test_myisam_lock` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE = MyISAM DEFAULT CHARSET = utf8;

-- 插入幾條資料
INSERT INTO `blog_test`.`test_myisam_lock` (`id`, `name`, `age`) VALUES (1, 'itwxe', 18);
INSERT INTO `blog_test`.`test_myisam_lock` (`id`, `name`, `age`) VALUES (2, 'Lee Patel', 62);
INSERT INTO `blog_test`.`test_myisam_lock` (`id`, `name`, `age`) VALUES (3, 'Sakurai Mio', 55);
INSERT INTO `blog_test`.`test_myisam_lock` (`id`, `name`, `age`) VALUES (4, 'Tan Xiuying', 42);
INSERT INTO `blog_test`.`test_myisam_lock` (`id`, `name`, `age`) VALUES (5, 'Cheng Yunxi', 47);

1、加讀鎖

本文表格中序號代表兩個session中SQL的執行順序,同一行為從左往右執行的語句。圖中已標註當前示例SQL執行順序,後續不再截圖說明。

序號 session1 session2
1 lock table test_myisam_lock read;
2 select * from test_myisam_lock;
-- 可以查詢
select * from test_myisam_lock;
-- 可以查詢
3 insert into blog_test.test_myisam_lock ( name, age) values ('xiaowu', 18);
-- 插入失敗,報錯
insert into blog_test.test_myisam_lock ( name, age) values ('xiaowu', 18);
-- 等待執行
4 unlock tables; -- 釋放鎖後沒有超時則執行插入操作

加讀鎖

對MyISAM表的讀操作(加讀鎖),不會阻塞其他程式對同一表的讀請求;但會阻塞對同一表的寫請求,當前執行緒無法執行寫操作,其他執行緒進入等待狀態,只有當讀鎖釋放後,才會執行其它程式的寫操作。

2、加寫鎖

序號 session1 session2
1 Lock table test_myisam_lock write;
2 select * from test_myisam_lock;
-- 可以查詢
select * from test_myisam_lock;
-- 進入等待執行狀態,手動取消(ctrl + c)
3 insert into blog_test.test_myisam_lock ( name, age) values ('xiaoer', 18);
-- 插入成功
insert into blog_test.test_myisam_lock ( name, age) values ('xiaoer', 18);
-- 進入等待執行狀態,手動取消(ctrl + c)
4 update blog_test.test_myisam_lock set name = 'wangxiaoer' where id = 8;
-- 更新成功
update blog_test.test_myisam_lock set name = 'wangxiaoer' where id = 8;
-- 進入等待執行狀態,手動取消(ctrl + c)
5 delete from blog_test.test_myisam_lock where id = 8;
-- 刪除成功
delete from blog_test.test_myisam_lock where id = 8;
-- 進入等待執行狀態
6 unlock tables; -- 釋放鎖後沒有超時則進行刪除操作

簡單來說:對MylSAM表的寫操作(加寫鎖),不會阻塞當前程式對錶的讀寫操作;但是會阻塞其他程式對同一表的讀和寫操作,只有當寫鎖釋放後,才會執行其它程式的讀寫操作。

2. 行鎖

每次操作鎖住一行資料,開銷大,加鎖慢,會出現死鎖,鎖定粒度最小,發生鎖衝突的概率最低,併發度最高。

同時InnoDB與MyISAM最大的兩點不同就是支援事務支援行級鎖

基本操作

-- 建立示例表
CREATE TABLE `account` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `balance` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- 插入幾條資料
INSERT INTO `blog_test`.`account` (`id`, `name`, `balance`) VALUES (1, 'itwxe', 1000);
INSERT INTO `blog_test`.`account` (`id`, `name`, `balance`) VALUES (2, 'Kenneth Adams', 2000);
INSERT INTO `blog_test`.`account` (`id`, `name`, `balance`) VALUES (3, 'Takada Daichi', 3000);
INSERT INTO `blog_test`.`account` (`id`, `name`, `balance`) VALUES (4, 'Anne Russell', 4000);
INSERT INTO `blog_test`.`account` (`id`, `name`, `balance`) VALUES (5, 'Jia Yuning', 5000);

預設的RR隔離級別下示例,即可重複讀(repeatable-read)。

序號 session1 session2
1 begin;
-- 開啟事務
begin;
-- 開啟事務
2 update account set balance = balance + 1000 where id = 1;
-- 更新id = 1的餘額
select * from account where id = 1;
-- 查詢不阻塞,正常查詢
3 update account set balance = balance + 1000 where id = 1;
-- 更新操作阻塞,進入等待執行狀態,最後會超時或者等待session1提交(回滾)事務後才會執行更新語句。
4 commit;
-- 提交事務
-- session1提交(回滾)事務後,沒有超時則進行更新操作

當然啦,小二這裡寫的只有兩個會話,如果有很多個session同時更新一行資料也是一樣進入等待狀態。

簡單來說:在非序列化事務隔離級別下,InnoDB在執行查詢語句select時不會加鎖,但是update、insert、delete操作會加行鎖。

當然,當查詢語句也需要加鎖也是可以的,使用 select ... for update

select * from account where id = 1 for update;

此時當前會話對於id = 1的行可以進行增刪改查操作,其它會話不能對id = 1 行進行操作。只有等待當前會話釋放鎖(提交/回滾事務)後才能夠操作記錄id = 1的行。

五、行鎖與事務隔離級別案例分析

行鎖支援事務所以當然就得結合事務隔離級別來分析實操一下。

1. 讀未提交

首先將示例賬戶餘額還原到1000,update account set balance = 1000 where id = 1;

序號 session1 session2
1 set tx_isolation = 'read-uncommitted';
-- 設定當前回話事務隔離級別為讀未提交
set tx_isolation = 'read-uncommitted';
-- 設定當前回話事務隔離級別為讀未提交
2 begin;
-- 開啟事務
3 select * from account where id = 1;
-- 第一次查詢餘額為1000
begin;
-- 開啟事務
4 update account set balance = balance + 1000 where id = 1;
-- 更新id = 1的餘額
5 select * from account where id = 1;
-- 第二次查詢餘額為2000,這裡就是髒讀了,讀到了沒有提交的資料
6 rollback;
-- 回滾
7 select * from account where id = 1;
-- 第三次查詢餘額為1000
8 update account set balance = balance - 2000 where id = 1;
-- 例如序號5中的餘額在Java程式碼中判斷可以購買後執行了扣減餘額
9 commit;
-- 提交事務

可以看到,讀未提交隔離級別下,第5行session讀取到的賬戶餘額就是2000了,這個時候如果Java應用程式使用2000作為判斷了某些業務操作,例如判斷是否可以購買一個2000的商品,此時session又還沒有扣減餘額,那麼當session2因為某些原因回滾了,此時id為1的使用者餘額就是 -1000 塊了,這是明顯不合理的;並且實際開發中一定不要使用Java程式計算後直接將值更新到資料庫,這樣會造成髒寫,例如Java程式計算值後賬戶餘額為零,那麼執行update account set balance = 0 where id = 1;的話問題就更大了。

為了解決這個問題就要用到讀已提交的隔離級別了。

2. 讀已提交

首先將示例賬戶餘額還原到1000,update account set balance = 1000 where id = 1;

序號 session1 session2
1 set tx_isolation = 'read-committed';
-- 設定當前回話事務隔離級別為讀已提交
set tx_isolation = 'read-committed';
-- 設定當前回話事務隔離級別為讀已提交
2 begin;
-- 開啟事務
3 select * from account where id = 1;
-- 第一次查詢餘額為1000
begin;
-- 開啟事務
4 update account set balance = balance + 1000 where id = 1;
-- 更新id = 1的餘額
5 select * from account where id = 1;
-- 第二次查詢餘額為1000
6 commit;
-- 提交事務
7 select * from account where id = 1;
-- 第三次查詢餘額為2000
8 commit;
-- 提交事務

可以看到已經解決了髒讀的問題,但是在session2提交後出現了不可重複讀的問題,不可重複讀會讓我們在編寫Java應用程式碼的時候很難編寫,而不可重複讀就得靠可重複讀的隔離級別來解決了。

3. 可重複讀

首先將示例賬戶餘額還原到1000,update account set balance = 1000 where id = 1;

序號 session1 session2
1 set tx_isolation='repeatable-read';
-- 設定當前回話事務隔離級別為可重複讀
set tx_isolation='repeatable-read';
-- 設定當前回話事務隔離級別為可重複讀
2 begin;
-- 開啟事務
3 select * from account;
-- 第一次查詢id = 1賬戶餘額為1000
begin;
-- 開啟事務
4 update account set balance = balance + 1000 where id = 1;
-- 更新id = 1的餘額
5 select * from account;
-- 第二次查詢id = 1賬戶餘額為1000
6 commit;
-- 提交事務
7 select * from account;
-- 第二次查詢id = 1賬戶餘額為1000,可以看到已經解決了不可重複讀的問題
8 begin;
-- 重新開啟一個事務,驗證幻讀問題
9 insert into account (id, name, balance) values (6, 'xiaoer', 6000);
-- 插入一行資料
commit;
-- 提交事務
10 select * from account;
-- 結果和序號7中展示的結果一致,只有id = 1~5的記錄
11 update account set balance = 6666 where id = 6;
-- 更新新增的id = 6記錄
12 select * from account;
-- 再次發現查詢到的記錄多了id = 6的記錄,且id=6的賬戶餘額為6666
13 commit;

所以,可以看到其實MySQL並沒有完全解決幻讀的問題,在RR級別下當MySQL有可能會出現幻讀問題

當然我們實際開發通常也不會這樣去操作,因為id = 6這個對當前session是不可見的,所以通常不會直接修改到其他session插入的記錄。

所以MySQL預設的事務隔離級別就是RR級別,併發高且能解決99.99%的業務場景。

4. 序列化

首先將示例賬戶餘額還原到1000,update account set balance = 1000 where id = 1;

序號 session1 session2
1 set tx_isolation='serializable';
-- 設定當前回話事務隔離級別為序列化
set tx_isolation='serializable';
-- 設定當前回話事務隔離級別為序列化
2 begin;
-- 開啟事務
begin;
-- 開啟事務
3 select * from account where id = 1;
-- 查詢id = 1資訊,賬戶餘額為1000
select * from account where id = 2;
-- id = 2的記錄可以查詢
4 select * from account where id = 1;
-- id = 1賬戶餘額為1000
5 select * from account where id = 2;
-- 進入等待執行狀態,最後會超時或者等待session1提交事務或者回滾後才會執行查詢。
6 commit; -- 如果沒有超時則執行查詢id = 2的記錄資訊
7 commit;

可以看到序列化隔離級別下,使用的是排它鎖,鎖定記錄的增刪改查都會收到影響進入等待執行狀態。

所以,序列化隔離級別下不會有幻讀的問題,當然效率也最低,通常實際專案開發中都不會使用。

六、行鎖詳解

1. 記錄鎖、間隙鎖、臨鍵鎖

行鎖有三個型別,分別是記錄鎖、間隙鎖、臨鍵鎖,記錄鎖只鎖定一行記錄,而間隙鎖和臨鍵鎖都是鎖定一個範圍的記錄行。

記錄鎖(Record Locks)

記錄鎖鎖定一行記錄的已經在鎖分類中演示過了 id = 1 的場景,不嘮叨了。

間隙鎖(Gap Locks)

間隙鎖指的是鎖的是兩個值之間的間隙,可以解決RR級別下一些情景的幻讀問題。

-- 再插入幾條資料演示間隙鎖和臨鍵鎖
INSERT INTO `blog_test`.`account` (`id`, `name`, `balance`) VALUES (13, 'itwxe.com', 13000);
INSERT INTO `blog_test`.`account` (`id`, `name`, `balance`) VALUES (18, 'zhangsan', 18000);
INSERT INTO `blog_test`.`account` (`id`, `name`, `balance`) VALUES (25, 'lisi', 25000);

插入資料後id就產生了(6, 13), (13, 18), (18, 25), (25, 正無窮) 這四個空間間隙。

下面使用RR事務隔離級別,set tx_isolation='repeatable-read';

序號 session1 session2
1 begin;
-- 開啟事務
begin;
-- 開啟事務
2 update account set balance = balance + 1000 where id > 8 and id < 13;
-- 查詢製造間隙(8, 13)
insert into account values(6, 'jianxi', 6000);
-- 可以正常執行響應結果,但是主鍵已存在,插入失敗
3 insert into account values(7, 'jianxi', 7000);
-- 插入id = 10的記錄會發現插入不了,進入等待執行狀態
4 insert into account values(10, 'jianxi', 8000);
-- 插入id = 10的記錄會發現插入不了,進入等待執行狀態,手動取消(ctrl + c)
5 insert into account values(13, 'jianxi', 8000);
-- 插入id = 13同樣會阻塞,進入等待執行狀態
6 insert into account values(14, 'jianxi', 8000);
-- 可以正常插入
7 rollback;
-- 回滾事務
rollback;
-- 回滾事務

可以看到測試中產生鎖定的範圍為(6, 13],也就是說間隙鎖鎖定的範圍是小範圍(id=8)前一條行記錄(id=6)的開區間到大範圍(id=13)的後一條記錄,左開右閉區間。

可以看到間隙鎖在減少了某些情況下幻讀的發生,通過對間隙加鎖讓間隙內在session1提交(回滾)事務之前其他session都無法進行插入或者修改任何資料。

間隙鎖是在可重複讀隔離級別下才會生效。

臨鍵鎖(Next-key Locks)

臨鍵鎖可以說是特殊的間隙鎖,即行鎖和間隙鎖的結合,可以理解為間隙鎖中包含記錄行。

目前存在(6, 13), (13, 18), (18, 25), (25, 正無窮) 這四個空間間隙。

下面使用RR事務隔離級別,set tx_isolation='repeatable-read';

序號 session1 session2
1 begin;
-- 開啟事務
begin;
-- 開啟事務
2 update account set balance = balance + 1000 where id > 15 and id < 28;
-- 查詢製造間隙(8, 13)
insert into account values(13, 'linjian', 13000);
-- 可以正常執行響應結果,但是主鍵已存在,插入失敗
3 insert into account values(17, 'linjian', 17000);
-- 插入id = 17的記錄會發現插入不了,進入等待執行狀態
4 insert into account values(18, 'linjian', 18000);
-- 插入id = 18的記錄會發現插入不了,進入等待執行狀態,這就是包含的行鎖。手動取消(ctrl + c)
5 insert into account values(25, 'linjian', 25000);
-- 插入id = 25同樣會阻塞,進入等待執行狀態
6 insert into account values(29, 'linjian', 29000);
-- 插入id = 29同樣會阻塞,進入等待執行狀態
7 rollback;
-- 回滾事務
8 -- 如果這個時候還沒有超時則執行插入id = 29的記錄
9 rollback;
-- 回滾事務

可以看到臨鍵鎖鎖定的範圍是(13,正無窮]。同樣是左開右閉,取不到13而取得到正無窮。

2. 無索引行鎖升級表鎖

序號 session1 session2
1 begin;
-- 開啟事務
begin;
-- 開啟事務
2 update account set balance = balance + 1000 where name = 'itwxe';
-- 進行更新操作,name列沒有索引
select * from account where id = 2;
-- 升級表鎖可以查詢
3 insert into account values(14, 'linjian', 13000);
-- 升級表鎖無法插入,進入等待狀態
4 rollback;
-- 回滾事務,當然提交也可以
5 -- 如果這個時候還沒有超時則執行插入id = 14的記錄
6 rollback;
-- 回滾事務,當然提交也可以

看到這裡都是耐心的小夥伴啦,InnoDB的行鎖是針對索引加的鎖,不是針對記錄加的鎖。並且該索引不能失效,否則會從行鎖升級為表鎖。

所以,如果對非索引欄位更新,行鎖會升級成表鎖,升級成表鎖後任意行的寫操作都會阻塞,直到表鎖被釋放。

3. 行鎖分析

可以通過檢查innodb_row_lock狀態變數來分析MySQL行鎖的情況。

show status like 'innodb_row_lock%'

行鎖分析

  • Innodb_row_lock_current_waits:當前正在等待鎖定的數量
  • Innodb_row_lock_time:從MySQL啟動到現在鎖定總時間長度
  • Innodb_row_lock_time_avg:每次等待的平均時間
  • Innodb_row_lock_time_max:MySQL啟動到現在最長的一次等待時間
  • Innodb_row_lock_waits:MySQL啟動後到現在總共等待的次數

當等待次數很多,並且每次等待時長也很長時,就要分析系統中為什麼會有這麼多的等待,然後根據分析結果著手製定優化計劃。

4. 系統庫與鎖相關的表

MySQL系統庫中幾個與鎖相關的表,有時開發中debug除錯碰到一直卡著SQL語句不執行,這時候完全有可能是其他小夥伴也在除錯這一行資料,然後一直鎖定了那行資料所以你就除錯不下去了,那麼咋辦嘞?

MySQL給我們提供了表來查詢,只要kill掉其他小夥伴的事務自然你就可以除錯了,不過輕易不要嘗試,因為你kill掉那個事務後其他小夥伴如果沒除錯完那麼就白除錯了,所以被揍了小二概不負責?

‐‐ 檢視事務
select * from INFORMATION_SCHEMA.INNODB_TRX;

‐‐ 檢視鎖
select * from INFORMATION_SCHEMA.INNODB_LOCKS;

‐‐ 檢視鎖等待
select * from INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

‐‐ 釋放鎖,trx_mysql_thread_id是INNODB_TRX表中查詢的資訊的值,例如`kill 394`,kill掉的事務無法提交也無法回滾,會包ERROR錯誤
kill trx_mysql_thread_id;

‐‐ 檢視鎖等待詳細資訊
show engine innodb status\G;

5. 死鎖

序號 session1 session2
1 set tx_isolation='repeatable-read';
-- 設定當前回話事務隔離級別為可重複讀
set tx_isolation='repeatable-read';
-- 設定當前回話事務隔離級別為可重複讀
2 begin;
-- 開啟事務
begin;
-- 開啟事務
3 select * from account where id = 1 for update;
-- 正常查詢
select * from account where id = 2 for update;
-- 正常查詢
4 select * from account where id = 2 for update;
-- 進入等待狀態
select * from account where id = 1 for update;
-- 報錯,檢測到死鎖,重新啟動事務
-- ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
5 -- 因為session事務被重啟,所以id = 2的行鎖被釋放,所以沒有超時的情況下執行查詢 rollback;
6 rollback;

死鎖

大多數情況下MySQL可以自動檢測死鎖並回滾產生死鎖的那個事務,但是有些情況MySQL沒法自動檢測死鎖。

6. 鎖優化建議

  • 儘可能讓所有資料檢索都通過索引來完成,避免無索引行鎖升級為表鎖。
  • 儘可能減少檢索條件範圍,避免間隙鎖。
  • 儘量控制事務大小,減少鎖定資源量和時間長度,涉及事務加鎖的sql儘量放在事務最後執行。

相關文章