死锁报错日志:
Deadlock found when trying to get lock; try restarting transaction
死锁报错示例:
1、各自锁住对方进程正在使用的行数据
譬如先执行:
-- session1
BEGIN;
UPDATE students SET memo='DLLock' WHERE dbid =9;
-- session2
BEGIN;
UPDATE students SET memo='DLLock' WHERE dbid =7;
再执行:
-- session1
UPDATE students SET memo='DLLock' WHERE dbid =7;
-- session2
UPDATE students SET memo='DLLock' WHERE dbid =9;
再比如下面的情况:
-- session1
BEGIN;
UPDATE students SET memo='DLLock' WHERE dbid =9;
-- session2
BEGIN;
SELECT dbid FROM students WHERE dbid<20 for UPDATE;
执行完以上后再在session1中执行如下,即可产生死锁:
UPDATE students SET memo='DdLock' WHERE dbid =1;
因为session2中的 dbid<20 会先对小于9的数据先加锁(id-9已经被session1提前加锁),此时session1再对id-1的数据执行加锁,就会产生争用,从而产生死锁。
2、批量入库,不存在则新增,存在则更新的情况:
假设目前 students 表中不存在 dbid 为 19 和 20 的数据,此时需要新增
-- session1
BEGIN;
SELECT * FROM students WHERE dbid =19 for UPDATE;
-- session2
BEGIN;
SELECT * FROM students WHERE dbid =20 for UPDATE;
执行完以上后再在各自的session中中执行插入操作,即可产生死锁:
-- session1
INSERT INTO `students` (`dbid`, `uid`, `uname`) VALUES (19, 39, '小明明');
-- session2
INSERT INTO `students` (`dbid`, `uid`, `uname`) VALUES (20, 35, '小红红');
为何两条不存在的数据也会产生死锁,是因为:
- 当对已存在的行进行锁定时(主键),mysql就只有行锁。
- 当对未存在的行进行锁的时候(即使条件为主键),mysql会锁住一段范围(即gap锁),其锁范围为:
(无穷小或小于表中锁住id的最大值,无穷大或大于表中锁住id的最小值)
如果表中目前有已有的id为18,那么就锁住 [19,无穷大);
如果表中目前已有的 id 区间为(11 , 30),那么就锁住[12,29];
所以示例2中的两个session其实锁住的是相同 gap 中的数据,因此执行插入时才会产生 dead-lock;对于这种此类示例2的死锁解决办法是用mysql特有的语法 ON DUPLICATE KEY UPDATE来解决此问题;
该语法的意思为:
- 在insert时候,如果insert的数据会引起唯一索引(包括主键索引)的冲突,即唯一值重复了,则不会执行insert操作,而执行后面的update操作。
如下两条语句最终的执行效果相同:
-- 1
INSERT INTO table (a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1;
-- 2
UPDATE table SET c=c+1 WHERE a=1;
因为相对于主键来说,insert语句,插入的行不管是否存在,都只有行锁。
就到这里吧!