MySQL死锁问题怎么解决?手写出死锁情况。
昨天看到一位网友分享阿里的面经,看到这个问题,我在心里想了想如果自己被问到能不能比较系统、体系的回答这个问题?答案是否定的。于是写了这篇文章来查缺补漏,梳理MySQL死锁的问题。
一、什么是死锁
死锁是指两个或多个事务在执行过程中,因相互等待对方释放资源而陷入的一种等待状态,如果没有外力的干预,将永远无法继续执行。
简而言之,就是事物A等待事物B释放资源,而事物B也在等待事物A释放资源,最终僵持着无法继续执行。
二、什么原因导致死锁
-
事物竞争资源
在高并发情况下,多个事务可能同时对同一资源进行操作。当一个事务占用了某个资源,而另一个事务也需要这个资源时,就会形成资源竞争。两个事务都坚持等待对方释放资源,就会导致死锁。
-
锁定顺序设置不当
两个事务或多个事物在锁定资源时采取的顺序不一致,导致死锁。
例如:事务A先锁住资源1再锁住资源2,而事务B先锁住资源2再锁住资源1。这种交叉锁定的顺序会导致两个事务相互等待,从而引发死锁。
-
事物长期持有锁
如果一个事务等待另一个事务释放资源的时间过长,而没有采取适当的措施,如超时回滚等,也可能导致死锁。
-
事物隔离级别设置不当
MySQL支持四种事务隔离级别,Read Uncommitted、Read Committed、Repeatable Read、Serializable。不同的隔离级别对并发访问的控制程度不同,可能引发不同的死锁问题。在READ COMMITTED隔离级别下,会出现幻读问题,从而可能引发死锁。
三、如何避免死锁
- 事务进行合理的并发度控制
- 事物尽量按相同顺序访问数据
- 设置合理的锁等待时间
- 设置合理的事务隔离级别
- 合理设计数据库表结构
- 定期检查数据库的性能指标、日志和错误信息,及时发现潜在的死锁问题。
五、怎么解决死锁
- 手动回滚事物
检查到死锁后,可以手动选择一个事物回滚,让其释放资源。
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 = 1
UPDATE test1 SET t1 = t1 + 100 WHERE id = 2; -- 尝试锁定 id = 2,等待事务 B 提交
COMMIT;
-- 事务 B
BEGIN;
UPDATE test1 SET t1 = t1 - 100 WHERE id = 2; -- 锁定 id = 2
UPDATE test1 SET t1 = t1 + 100 WHERE id = 1; -- 尝试锁定 id = 1,等待事务 A 提交
COMMIT;
死锁模拟流程图
执行过程如下:
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 提交