关于并发insert操作发生deadlock这个情况,一直有很多争议,而且网上的帖子所有的例证和模拟其实不一定反映了真实的情况,例如:
https://www.percona.com/blog/2012/03/27/innodbs-gap-locks/
http://www.cnblogs.com/LBSer/p/5183300.html
http://www.cnblogs.com/bamboos/p/3532150.html
不少的类似例子应该来说其实不少有猜测和为了重现而重现的目的。
事实上开两个窗口,不做delete/update/select for update,仅select/insert是100%可以重现死锁场景的,也可以不出现。
start transaction A窗口select from X where x=1,不存在则insert X表记录1。假设x存在索引。
start transaction B窗口select from X where x=2,不存在则insert X表记录2。
这取决于当前使用的事务隔离级别,不同的事务隔离级别下有不同的行为。
A 会话
USE memdb;
SHOW VARIABLES LIKE ‘%autocommit%‘;
SET tx_isolation=‘SERIALIZABLE‘;
SHOW VARIABLES LIKE ‘%tx%‘;
START TRANSACTION;
SELECT * FROM `abcdefg` WHERE def=19 AND ghi=19 AND jkl=19;
-- 暂停,等B窗口前半部分执行了在执行下一部分
INSERT INTO `abcdefg`(abc,def,ghi,jkl,mnp) VALUES(‘19‘,19,19,19,1);
COMMIT;
USE memdb;
SHOW VARIABLES LIKE ‘%autocommit%‘;
SET tx_isolation=‘SERIALIZABLE‘;
SHOW VARIABLES LIKE ‘%tx%‘;
START TRANSACTION;
SELECT * FROM `abcdefg` WHERE def=18 AND ghi=18 AND jkl=18;
-- 暂停,等A窗口前半部分执行了在执行下一部分
INSERT INTO `abcdefg`(abc,def,ghi,jkl,mnp) VALUES(‘18‘,18,18,18,1);
COMMIT;
原因在mysql的行锁实现主要分为记录锁、Gap锁、Next-key锁等。
这三种锁定的范围不同,并且行锁不代表实现时锁定了所有已存在的符合where中明确说明的条件的行,还有可能包括隐式、不存在的记录。
因为 SERIALIZABLE使用的是GAP锁,所以,insert时>=当前索引的所有记录都被加上了S锁,故此问题发生 。
这也是我们遇到的,非常纯粹的只有insert操作的死锁的例子,对于使用oracle、sql server的开发人员来说时,遇到时有时候相当的不理解。
具体见各类锁的区别http://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html。