电脑学堂
第二套高阶模板 · 更大气的阅读体验

数据库死锁解决办法:轻松应对程序卡顿问题

发布时间:2025-12-14 19:32:24 阅读:10 次

数据是怎么回事

你有没有遇到过这样的情况?系统运行得好好的,突然某个操作卡住不动,刷新页面也没用,后台日志里还冒出“Deadlock found when trying to get lock”这种提示。这就是数据库死锁在作怪。

简单来说,死锁就是两个或多个事务互相等待对方释放资源,结果谁都动不了,像两辆车在窄路上互不相让,堵成一团。

常见的死锁场景

比如电商平台做秒杀活动,用户A正在修改商品库存,同时又去查订单状态;而用户B刚好反过来,先查订单再改库存。如果这两个操作涉及的表加锁顺序不一致,就容易撞上死锁。

另一个典型例子是后台定时任务和前台业务抢资源。比如统计脚本批量更新用户积分,而用户此时正好在下单扣积分,两张表交叉加锁,一不小心就锁死。

怎么发现死锁

MySQL 提供了命令可以查看最近一次死锁信息:

SHOW ENGINE INNODB STATUS\G

执行后在输出结果里找 LATEST DETECTED DEADLOCK 这一节,里面会清楚写明哪两个事务、在哪张表、因为什么语句导致了死锁。

优化索引减少锁冲突

很多时候死锁是因为全表扫描导致锁定过多行。比如有个查询没有走索引,数据库只好把整张表锁住,别人想改一行都得等。

给经常用于查询条件的字段加上索引,能让数据库精准定位数据,只锁必要的行。比如 user_id、order_status 这类字段,建好索引后能大大降低锁的范围。

统一操作顺序避免循环等待

程序里涉及到多表更新时,约定一个固定的执行顺序。比如 always 先更新订单表,再更新库存表,大家按同一个顺序来,就不会出现“A等B、B等A”的尴尬局面。

这个规则可以在开发规范里写清楚,新人接手代码也知道该怎么写。

缩短事务生命周期

有些事务包了一大堆操作,从读数据到发通知全塞在一个事务里,执行时间长,锁持有久,别人排队等到天荒地老。

把不必要的操作移出事务,只保留核心的读写逻辑。比如发送邮件、记录日志这些,完全可以放到事务提交后再异步处理。

合理使用重试机制

死锁无法完全避免,但程序要有应对能力。当捕获到死锁异常时,自动重试事务通常就能解决问题。

以 Java 为例:

int retries = 3;
for (int i = 0; i < retries; i++) {
try {
executeTransaction();
break;
} catch (DeadlockLoserDataAccessException e) {
if (i == retries - 1) throw e;
Thread.sleep(50 * (i + 1));
}
}

重试之间加个短暂延迟,错开竞争时机,成功率会高很多。

调整隔离级别降低锁强度

如果不是特别严格的业务场景,可以把事务隔离级别从 REPEATABLE READ 降到 READ COMMITTED。这样间隙锁会减少,插入新记录时的锁冲突也会变少。

设置方式:

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

注意要评估业务影响,比如是否允许幻读。

监控与预警不能少

线上系统建议配置死锁日志采集,通过 ELK 或 Prometheus 抓取 SHOW ENGINE INNODB STATUS 的内容,一旦发现死锁自动报警。

定期分析死锁日志,找出高频冲突点,针对性优化 SQL 或调整业务流程,比等问题爆发后再处理要主动得多。