MySQL死锁问题
# MySQL死锁问题
# 前言
在日常开发中,我们经常会遇到数据库死锁问题。就像交通堵塞一样,当多个事务互相等待对方释放资源时,就形成了死锁。本文将围绕InnoDB存储引擎中的死锁问题,详细介绍相关概念、产生原因、典型场景以及解决方案。
# 相关概念
# 并发控制
并发控制(Concurrency Control)是数据库管理系统用于保证数据一致性的重要机制。
# 锁的类型
MySQL实现并发控制主要通过两种类型的锁:
- 共享锁(Shared Lock,S锁):也叫读锁,允许多个事务同时读取同一资源
- 排他锁(Exclusive Lock,X锁):也叫写锁,一个事务获取了写锁后,其他事务无法获取该资源的读锁或写锁
# 锁粒度
InnoDB存储引擎支持多种粒度的锁:
- 表锁:锁定整张表,开销小但并发度低
- 行锁:只锁定涉及的数据行,开销较大但并发度高
- 间隙锁(Gap Lock):锁定索引记录之间的间隙,防止幻读
- Next-Key Lock:行锁和间隙锁的组合
# 事务的特性
事务必须满足ACID特性:
- 原子性(Atomicity):事务是不可分割的工作单位,要么全部执行,要么全部回滚
- 一致性(Consistency):事务执行前后数据库必须保持一致状态
- 隔离性(Isolation):事务执行过程中的中间状态对其他事务不可见
- 持久性(Durability):事务一旦提交,其修改将永久保存在数据库中
# 事务隔离级别
MySQL支持四种事务隔离级别:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED | 可能 | 可能 | 可能 |
READ COMMITTED | 不可能 | 可能 | 可能 |
REPEATABLE READ(默认) | 不可能 | 不可能 | 可能 |
SERIALIZABLE | 不可能 | 不可能 | 不可能 |
# 死锁的定义与危害
# 什么是死锁
死锁是指两个或多个事务在执行过程中,因争夺资源而造成的一种互相等待的现象。比如:
- 事务A持有资源1,等待资源2
- 事务B持有资源2,等待资源1 这样两个事务就陷入了互相等待的死锁状态。
# 死锁的危害
- 事务无法完成,系统资源被长期占用
- 降低系统性能和吞吐量
- 可能引发连锁反应,导致更多事务被阻塞
# 死锁的典型场景
以下示例基于InnoDB存储引擎,隔离级别为REPEATABLE READ。
# 场景一:互相请求对方持有的锁
-- 准备数据
CREATE TABLE `test` (
`id` int NOT NULL,
`value` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
INSERT INTO test VALUES (1, 10), (2, 20);
-- Session A
START TRANSACTION;
UPDATE test SET value = 11 WHERE id = 1;
-- Session B
START TRANSACTION;
UPDATE test SET value = 21 WHERE id = 2;
-- Session A
UPDATE test SET value = 12 WHERE id = 2; -- 等待Session B释放锁
-- Session B
UPDATE test SET value = 22 WHERE id = 1; -- 死锁发生
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 场景二:间隙锁导致的死锁
CREATE TABLE `orders` (
`id` int NOT NULL,
`status` int DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB;
-- Session A
START TRANSACTION;
SELECT * FROM orders WHERE status = 1 FOR UPDATE;
-- Session B
START TRANSACTION;
INSERT INTO orders VALUES (5, 1); -- 被Session A的间隙锁阻塞
-- Session A
INSERT INTO orders VALUES (6, 1); -- 死锁发生
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 死锁的预防和处理
# 预防措施
按固定顺序访问表和行
- 对多个表进行操作时,应该按照相同的顺序
- 批量更新数据时,按主键或索引顺序进行更新
合理设计事务
- 保持事务尽量短小
- 一次性锁定所需要的所有资源
- 避免事务中的用户交互
优化表结构和索引
- 合理设计索引,避免全表扫描
- 避免使用过多的锁定范围
应用层优化
- 使用乐观锁替代悲观锁
- 适当的重试机制
- 合理设置事务隔离级别
# 死锁检测和处理
InnoDB有两种处理死锁的方式:
等待超时
- 通过
innodb_lock_wait_timeout
参数设置 - 默认值为50秒
- 通过
死锁检测
- 通过
innodb_deadlock_detect
参数控制 - 发现死锁后,会回滚代价较小的事务
- 通过
-- 查看和设置等待超时时间
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';
SET GLOBAL innodb_lock_wait_timeout = 50;
-- 查看死锁检测状态
SHOW VARIABLES LIKE 'innodb_deadlock_detect';
1
2
3
4
5
6
2
3
4
5
6
# 总结
死锁是并发数据库系统中难以完全避免的问题。通过:
- 理解死锁产生的原因
- 采用合理的预防措施
- 正确配置数据库参数
- 优化应用程序设计
我们可以最大限度地减少死锁的发生,提高系统的可用性和性能。
# 参考资料
- 《高性能MySQL》第三版
- MySQL官方文档:https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks.html
上次更新: 4/24/2025