一、问题/场景描述
在MySQL数据库高并发事务处理场景中,开发者偶尔会遇到“Deadlock found when trying to get lock”的错误提示。这通常意味着两个或更多的事务相互等待对方释放锁资源,导致所有相关事务都无法继续执行,形成死锁。死锁会严重影响应用系统的响应速度和稳定性。
二、原因分析
MySQL死锁的根本原因在于事务对数据资源的加锁顺序不一致。例如,事务A先锁定了记录1,再尝试锁定记录2;而事务B先锁定了记录2,再尝试锁定记录1。当这两个操作并发执行时,事务A等待事务B释放记录2的锁,同时事务B等待事务A释放记录1的锁,从而形成循环等待,即死锁。InnoDB存储引擎会自动检测死锁,并选择回滚其中一个代价较小的事务来打破僵局。
三、详细解决步骤
解决MySQL死锁通常遵循“先排查,后优化”的原则。以下是详细的排查与解决步骤。
步骤1:查看最近死锁信息
首先,通过MySQL命令查看最近发生的死锁日志,这是分析问题最直接的依据。需要确保InnoDB的监控状态已开启。
-- 开启InnoDB状态监控(临时)
SET GLOBAL innodb_status_output = ON;
SET GLOBAL innodb_status_output_locks = ON;
-- 查看详细的InnoDB状态,其中包含最近的死锁信息
SHOW ENGINE INNODB STATUSG
在输出的冗长信息中,找到“LATEST DETECTED DEADLOCK”部分,这里详细记录了导致死锁的事务、它们持有的锁和等待的锁。
步骤2:分析死锁日志并定位SQL
仔细阅读死锁日志。关键信息包括:
1. 涉及死锁的事务ID。
2. 每个事务正在执行的SQL语句(TRANSACTION部分下的SQL TEXT)。
3. 每个事务已经持有的锁(holds lock)和正在等待的锁(waits for)。
分析这些SQL的操作顺序和锁类型(记录锁、间隙锁等),找出加锁顺序不一致的根源。
步骤3:优化应用逻辑与SQL
根据分析结果,从应用层面进行优化,这是解决死锁的根本方法。
-- 1. 保持一致的访问顺序:确保不同事务对多个资源的操作顺序相同。
-- 例如,总是先更新表A,再更新表B。
-- 2. 减少事务粒度与时间:尽快提交事务,避免在事务内进行耗时操作。
BEGIN;
-- 只包含必要的数据库操作
UPDATE orders SET status = 'paid' WHERE id = 100;
UPDATE inventory SET stock = stock - 1 WHERE product_id = 5;
COMMIT;
-- 3. 为高频查询条件添加合适的索引,减少锁定的范围。
-- 假设user_id字段经常用于更新条件但无索引,考虑添加:
CREATE INDEX idx_user_id ON account_log(user_id);
步骤4:调整数据库配置(谨慎使用)
如果死锁发生频率极低且难以从逻辑上完全避免,可以考虑调整数据库参数作为临时或辅助手段。
# 编辑MySQL配置文件,如my.cnf
sudo vim /etc/mysql/my.cnf
# 在[mysqld]部分增加或修改以下参数
[mysqld]
# 降低死锁检测的间隔(默认值即可,通常不调整)
# innodb_deadlock_detect = ON
# 设置锁等待超时时间(单位:秒),超时后自动回滚当前语句,默认50秒。
innodb_lock_wait_timeout = 30
# 设置事务隔离级别为READ COMMITTED,可以减少间隙锁的产生,但需评估一致性影响。
# transaction-isolation = READ-COMMITTED
修改配置后需重启MySQL服务使配置生效。
四、注意事项
修改数据库隔离级别或锁超时参数可能带来副作用,如出现不可重复读、幻读等问题,需充分测试。根本解决之道在于优化应用代码和SQL语句。对于复杂业务,可以考虑使用队列将并发请求串行化处理,或使用乐观锁机制来避免行级锁竞争。
五、适用环境
本文介绍的方法适用于使用InnoDB存储引擎的MySQL 5.7及更高版本,特别是在Web应用等高并发场景。
