死锁主因是事务以不同顺序争抢行锁或索引间隙。典型场景包括:无索引WHERE导致全表加锁;多行交叉更新未排序;长事务混杂非DB操作;应建索引、单行更新、拆分事务、用Redis缓存统计。

频繁更新点赞计数本身不会直接导致死锁,真正触发死锁的是多个事务以不同顺序、不同方式争抢同一行或相邻索引间隙时形成的循环等待。论坛场景下,点赞操作看似简单,但高并发+设计松散极易踩中几个经典死锁坑。
UPDATE 无索引 WHERE 条件引发全表扫描加锁
比如用 UPDATE posts SET like_count = like_count + 1 WHERE title = 'MySQL死锁详解',而 title 没建索引——InnoDB 会退化为全表扫描,对所有行加意向排他锁(IX),再逐行判断是否匹配。这意味着一次点赞可能锁住成百上千条无关帖子记录,其他事务稍一碰这些行(哪怕只是查一下),就容易卷入锁冲突。
- 检查方法:执行
EXPLAIN看type是否为ALL,key是否为NULL - 修复动作:给
WHERE字段(如post_id)建主键或唯一索引;避免用非主键字段做更新条件 - 注意:即使
title有索引,若用了函数(如WHERE UPPER(title) = '...')或隐式转换(如字符串和数字比较),索引也会失效
多个事务交叉更新同一张表的不同行
典型场景是「用户 A 点赞帖子 1001 → 同时用户 B 点赞帖子 1002 → 用户 A 接着点踩帖子 1002 → 用户 B 接着点踩帖子 1001」。两个事务分别先持有了某行的 X 锁,又去请求对方已持有的行锁,形成 A→B→A 的闭环。
- 根本原因不是“更新多”,而是“顺序不一致”:事务没约定统一的行访问次序
- 实操解法:强制按
post_id升序处理。例如在应用层把要操作的多个帖子 ID 排序后再拼进 SQL:UPDATE posts SET like_count = like_count + 1 WHERE post_id IN (1001, 1002)→ 改为WHERE post_id IN (1001, 1002)(已排序) - 更稳妥的做法:单次只更新一个
post_id,避免一条 SQL 涉及多行,彻底消除交叉可能
点赞与评论/收藏等操作混在同一个长事务里
有些后端逻辑把「更新点赞数 + 插入点赞记录 + 更新用户积分 + 写操作日志」全塞在一个事务里。这会让 posts 表的行锁被持有几秒甚至更久,期间任何对同一帖子的读写(比如另一个用户查该帖详情带 SELECT ... FOR UPDATE)都会排队,锁等待链拉长后,死锁概率指数级上升。
- 关键判断:看事务里有没有非数据库操作(HTTP 调用、JSON 序列化、日志落盘等)
- 必须拆分:只把强一致性依赖的操作留在事务内(如扣库存、改状态),其余异步化或单独事务处理
- 补充技巧:对
like_count这类统计字段,可考虑用 Redis 做缓存层,DB 层改为定时聚合更新,避开高频行锁
最常被忽略的一点:死锁日志里显示的 SQL 往往只是“最后一根稻草”,真正的问题藏在它之前那几条没报错的语句里——比如一个事务先执行了无索引的 SELECT ... FOR UPDATE 锁了一堆间隙,后续点赞 UPDATE 才被卡住。所以光盯 UPDATE 不够,得顺藤摸瓜看整个事务的锁行为链条。


















