innodb的事務隔離級別是可重復讀級別且innodb_locks_unsafe_for_binlog禁用,也就是說允許next-key lock
實驗來自網上. ( 如果你沒有演示出來,請check order_id 是否是非unique key.) 如果你看不懂,請看后續文章. next-key lock (glap lock)完全解析.
CREATE TABLE `LockTest` (
`order_id` varchar(20) NOT NULL,
`id` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
KEY `idx_order_id` (`order_id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8
select * from LockTest;
empty set;
事務1 事務2
begin
delete from LockTest where order_id = 'D20'
begin
delete from LockTest where order_id = 'D19'
insert into LockTest (order_id) values ('D20')
insert into LockTest (order_id) values ('D19')
commit
commit
事務1 執行到insert語句會block住,事務2執行insert語句會提示死鎖錯誤
錯誤碼: 1213
Deadlock found when trying to get lock; try restarting transaction
Execution Time : 00:00:00:000
Transfer Time : 00:00:00:000
Total Time : 00:00:00:000
show engine innodb status 顯示死鎖信息
------------------------
LATEST DETECTED DEADLOCK
------------------------
2014-04-30 15:01:55 a233b90
*** (1) TRANSACTION:
TRANSACTION 596042, ACTIVE 7 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 320, 2 row lock(s), undo log entries 1
MySQL thread id 10851, OS thread handle 0x2abfb90, query id 251521 10.10.53.122 root update
insert into LockTest (order_id) values ('D20')
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 502 page no 4 n bits 72 index `idx_order_id` of table `test`.`LockTest` trx id 596042 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** (2) TRANSACTION:
TRANSACTION 596041, ACTIVE 19 sec inserting
mysql tables in use 1, locked 1
3 lock struct(s), heap size 320, 2 row lock(s), undo log entries 1
MySQL thread id 10848, OS thread handle 0xa233b90, query id 251522 10.10.53.122 root update
insert into LockTest (order_id) values ('D19')
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 502 page no 4 n bits 72 index `idx_order_id` of table `test`.`LockTest` trx id 596041 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 502 page no 4 n bits 72 index `idx_order_id` of table `test`.`LockTest` trx id 596041 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** WE ROLL BACK TRANSACTION (2)
簡單分析上面的場景先刪除再插入的sql是hibernage保存集合關聯的處理方式。delete語句刪除不存在且刪除的order_id大於現有表中的所有order_id,所以delete語句會使用next-key鎖住(當前最大-無窮大)
lock_id lock_trx_id lock_mode lock_type lock_table lock_index lock_space lock_page lock_rec lock_data
596133:502:4:1 596133 X RECORD `test`.`LockTest` idx_order_id 502 4 1 supremum pseudo-record
596134:502:4:1 596134 X RECORD `test`.`LockTest` idx_order_id 502 4 1 supremum pseudo-record
比較奇怪的是為啥兩個事務都拿到了相同區間的(當前最大-無窮大)的X鎖。不過換成read-commited級別后就沒死鎖了。
終於在官方文檔找到答案, 區間鎖只是用來防止其他事務在區間中插入數據,區間x鎖 與區間S鎖效果是一樣的。也就是說不會因為兩個事務都用加相同區間鎖而相互等待的
https://dev.mysql.com/doc/refman/5.1/en/innodb-record-level-locks.html
Gap locks in InnoDB are “purely inhibitive”, which means they only stop other transactions from inserting to the gap. Thus, a gap X-lock has the same effect as a gap S-lock.
當兩個事務拿到相同區間鎖后,就會阻止對方忘區間內做insert操作。所以第一個事務insert會阻塞,第二個事務會提示死鎖
詳情見 https://dev.mysql.com/doc/refman/5.6/en/innodb-record-level-locks.html
換成read-commited級別后就沒死鎖了! 因為沒有了間隙所,讀提交不需要幻讀控制,也就不需要間隙鎖了.
萬變歸一: 事務內加鎖總歸是為了隔離級別.
再來分析另外一個現象:
上述現象表明, delete/update會阻塞insert .那么換成先insert,再delete/update呢?
實驗表明不會阻塞? 這個感覺挺矛盾的,鎖的互斥是相對的. 主要原因是insert 不會產生 間隙鎖.
間隙鎖的作用本身就是單向的.
再次從事務內加鎖原因是為了隔離級別這個角度分析. insert
【解決方案有兩種】
1、改變程序中數據庫操作的邏輯
2、取消gap lock機制
innodb_locks_unsafe_for_binlog啟用
或者設置為隔離級別為讀提交
Gap locking can be disabled explicitly.This occurs if you change the transaction isolation level to READ COMMITTED orenable the innodb_locks_unsafe_for_binlog system variable.
3. 加上unique鎖
select for update / update where
1. 有該行 對非unique列會加 間隙共享鎖 和 行鎖 見 (14.2.2.4 InnoDB Record, Gap, and Next-Key Locks http://dev.mysql.com/doc/refman/5.7/en/innodb-record-level-locks.html)
2. 無該行 對非unique 會加間隙共享鎖 . 這個文檔比較麻煩. 要通過 上面(該文)
(Unexpected deadlock between concurrent INSERTs when unique key violation may occ http://bugs.mysql.com/bug.php?id=35821)和
(Deadlock detected on concurrent insert into same table (InnoDB) https://bugs.mysql.com/bug.php?id=43210 )
里面有個很好玩的案例,這兩個中文博客里也是該話題 (innodb next-key lock引發的死鎖 http://www.cnblogs.com/xhan/p/3701459.html)
---------------------
原文:https://blog.csdn.net/fei33423/article/details/46731891