网络学堂
霓虹主题四 · 更硬核的阅读氛围

数据库死锁解决办法:实战经验分享

发布时间:2025-12-14 19:32:48 阅读:321 次

项目上线前压力测试,系统突然卡住,日志里一堆报错提示“Deadlock found when trying to get lock”。这种情况不少见,尤其是多人同时操作数据的场景,比如电商平台抢购、媒体内容管理系统并发编辑时,数据就成了拦路虎。

死锁是怎么发生的?

简单说,就是两个或多个事务互相等待对方释放锁。比如事务A锁了表1,要去锁表2;事务B已经锁了表2,正等着表1。谁也不让,系统只能挑一个“牺牲者”回滚,MySQL就会抛出死锁异常。

常见于以下情况:

  • 多表更新顺序不一致
  • 长事务迟迟不提交
  • 索引缺失导致锁范围扩大

如何快速定位死锁?

在MySQL中,执行这条命令就能看到最近一次死锁详情:

SHOW ENGINE INNODB STATUS\G

输出内容很多,重点看“LATEST DETECTED DEADLOCK”这一段。里面会清楚告诉你哪个事务持有哪些锁,哪个事务在等,以及最终被回滚的是哪一个。根据这些信息,基本能还原出代码里是哪几条SQL出了问题。

避免死锁的实用方法

最有效的策略之一:统一访问顺序。假设你的业务逻辑要更新用户表和文章表,所有相关代码都先操作用户表,再操作文章表。这样即使并发,也不会出现交叉等待。

举个例子,媒体后台有两个功能:发布文章时扣减作者积分,另一个是管理员批量重置积分。如果一个先改文章后改用户,另一个反着来,就容易撞上。改成统一顺序,问题自然减少。

另一个关键是缩短事务时间。别在一个事务里做太多事,比如上传文件、调外部接口这些耗时操作,尽量挪到事务外。事务越短,持有锁的时间就越短,撞车概率越低。

合理使用索引

没索引的查询可能会让数据库锁住整张表,甚至更多行。比如一条UPDATE articles SET status = 'draft' WHERE author = '张三',如果author字段没索引,InnoDB可能得扫全表,锁住大量行,增加死锁风险。

加上索引后,数据库能精准定位,只锁必要的行。这就像快递员送件,有门牌号直接上楼,没门牌就得挨家敲门,还容易挡别人路。

程序层面怎么应对?

死锁没法完全杜绝,但程序要有容错能力。捕获数据库异常,发现是死锁(如MySQL错误码1213),自动重试一次事务。大多数情况下,第二次就能成功。

以Python为例:

import pymysql

retries = 0
while retries < 3:
    try:
        conn = pymysql.connect(...)
        with conn.cursor() as cursor:
            cursor.execute("START TRANSACTION")
            cursor.execute("UPDATE users SET score = score - 10 WHERE id = 1")
            cursor.execute("UPDATE articles SET status = 'published' WHERE id = 100")
            conn.commit()
            break
    except pymysql.err.OperationalError as e:
        if e.args[0] == 1213:  # Deadlock
            retries += 1
            continue
        else:
            raise
    finally:
        conn.close()

重试机制配合短事务,系统稳定性提升明显。

调整数据库配置

InnoDB有个参数叫innodb_deadlock_detect,默认开启。它会主动检测死锁并回滚其中一个事务。虽然消耗一点CPU,但在高并发场景建议保持开启。

另外,适当调小innodb_lock_wait_timeout,让等待锁的事务早点失败,避免长时间堆积。比如设为10秒:

SET innodb_lock_wait_timeout = 10;

这样系统反应更快,用户体验也好些。