MySQL 死锁产生原因及解决方法

死锁报错日志:

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语句,插入的行不管是否存在,都只有行锁。


就到这里吧!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容