MySQL死锁问题怎么解决?手写出死锁情况。

110 阅读4分钟

MySQL死锁问题怎么解决?手写出死锁情况。

昨天看到一位网友分享阿里的面经,看到这个问题,我在心里想了想如果自己被问到能不能比较系统、体系的回答这个问题?答案是否定的。于是写了这篇文章来查缺补漏,梳理MySQL死锁的问题。

一、什么是死锁

死锁是指两个或多个事务在执行过程中,因相互等待对方释放资源而陷入的一种等待状态,如果没有外力的干预,将永远无法继续执行。

简而言之,就是事物A等待事物B释放资源,而事物B也在等待事物A释放资源,最终僵持着无法继续执行。

二、什么原因导致死锁

  1. 事物竞争资源

    在高并发情况下,多个事务可能同时对同一资源进行操作。当一个事务占用了某个资源,而另一个事务也需要这个资源时,就会形成资源竞争。两个事务都坚持等待对方释放资源,就会导致死锁。

  2. 锁定顺序设置不当

    两个事务或多个事物在锁定资源时采取的顺序不一致,导致死锁。

    例如:事务A先锁住资源1再锁住资源2,而事务B先锁住资源2再锁住资源1。这种交叉锁定的顺序会导致两个事务相互等待,从而引发死锁。

  3. 事物长期持有锁

    如果一个事务等待另一个事务释放资源的时间过长,而没有采取适当的措施,如超时回滚等,也可能导致死锁。

  4. 事物隔离级别设置不当

    MySQL支持四种事务隔离级别,Read Uncommitted、Read Committed、Repeatable Read、Serializable。不同的隔离级别对并发访问的控制程度不同,可能引发不同的死锁问题。在READ COMMITTED隔离级别下,会出现幻读问题,从而可能引发死锁。

三、如何避免死锁

  1. 事务进行合理的并发度控制
  2. 事物尽量按相同顺序访问数据
  3. 设置合理的锁等待时间
  4. 设置合理的事务隔离级别
  5. 合理设计数据库表结构
  6. 定期检查数据库的性能指标、日志和错误信息,及时发现潜在的死锁问题。

五、怎么解决死锁

  1. 手动回滚事物

检查到死锁后,可以手动选择一个事物回滚,让其释放资源。

ROLLBACK;

2.设置自动检测死锁机制

InnoDB存储引擎具有自动检测死锁并回滚其中一个事务的机制。你可以通过配置innodb_lock_wait_timeout参数来设置锁等待的超时时间。

SET innodb_lock_wait_timeout=50;

3.终止死锁事物线程

可以通过命令SHOW ENGINE INNODB STATUS来获取死锁的日志信息,手动快速地找出被阻塞的事务及其线程ID,然后手动kill它,及时释放资源。

SHOW ENGINE INNODB STATUS; #获取死锁日志信息
KILL <pid>; #终止该事物

六、手写出死锁情况

以下是一个简单的死锁示例,涉及两个事务和两个表。

-- 创建test1表
CREATE TABLE test1 (
    id INT PRIMARY KEY,
    t1 INT
);
​
-- 插入测试数据
INSERT INTO Test1 (id, t1) VALUES (1, 1000);
INSERT INTO Test1 (id, t1) VALUES (2, 1000);
​
-- 事务 A
BEGIN;
UPDATE test1 SET t1 = t1 - 100 WHERE id = 1;  -- 锁定 id = 1UPDATE test1 SET t1 = t1 + 100 WHERE id = 2;  -- 尝试锁定 id = 2,等待事务 B 提交
COMMIT;
​
-- 事务 B
BEGIN;
UPDATE test1 SET t1 = t1 - 100 WHERE id = 2;  -- 锁定 id = 2UPDATE test1 SET t1 = t1 + 100 WHERE id = 1;  -- 尝试锁定 id = 1,等待事务 A 提交
COMMIT;
​

死锁模拟流程图

image.png

执行过程如下:

1.先创建表和插入数据

-- 创建test1表
CREATE TABLE test1 (
    id INT PRIMARY KEY,
    t1 INT
);
​
-- 插入测试数据
INSERT INTO Test1 (id, t1) VALUES (1, 1000);
INSERT INTO Test1 (id, t1) VALUES (2, 1000);

2.通过命令行界面执行开始事物

-- 事务 A
BEGIN;
UPDATE test1 SET t1 = t1 - 100 WHERE id = 1;  -- 锁定 id = 1
-- 事务 B
BEGIN;
UPDATE test1 SET t1 = t1 - 100 WHERE id = 2;  -- 锁定 id = 2
UPDATE test1 SET t1 = t1 + 200 WHERE id = 2;  -- 尝试锁定 id = 2,等待事务 B 提交
UPDATE test1 SET t1 = t1 + 400 WHERE id = 1;  -- 尝试锁定 id = 1,等待事务 A 提交

image-20250120184032887.png