资讯文章 资讯 · NEWS
两个人同时改一条条目:事务隔离级别、乐观锁,和目录站的并发写入
2026-10-24 约 1,735 字 预计阅读 4 分钟 yiyunkj
两
先把结论放在前面:大多数「改了半天没生效」不是数据库坏了,是读改写三步中间被别人插了一脚。应用层往往读出来、在内存里拼好、再整体写回,这三步之间如果有另一个事务成功提交,你的写回就把它覆盖了,双方都不报错。
这就是所谓的丢失更新(lost update),它最麻烦的地方在于没有异常、没有日志、过了几天才发现数据不对。

图片来源:网络,版权归原作者所有,侵权请联系删除
InnoDB 默认级别意味着什么
MySQL 官方文档写得很清楚:InnoDB 默认隔离级别是 REPEATABLE READ(可重复读),同一个事务里的一致读,读的是第一次读建立的快照。
真正影响并发写入的是后半句:在这个级别下,加锁读(FOR UPDATE / FOR SHARE)、UPDATE、DELETE 除了锁住命中的索引记录,还会锁住索引范围之间的间隙(间隙锁和 next-key 锁)。目的是防止别的事务往你扫过的范围里插入「幻行」。
这带来一个常被误判的现象:一条看起来只改一行的 UPDATE,实际锁住的是一段范围。尤其是 WHERE 条件走不到索引时,扫描过程会把整张表的记录和间隙都锁上,其他写入全堵在后面。
换成 READ COMMITTED 会松什么
READ COMMITTED 下,每次一致读都取自己的新快照,间隙锁在普通搜索和索引扫描中关闭,只保留外键检查和重复键检查。不匹配的行在评估完 WHERE 后立刻释放记录锁,半一致读也让 UPDATE 少等不少。
| 行为 | REPEATABLE READ | READ COMMITTED |
|---|---|---|
| 一致读快照 | 事务首读建立 | 每条语句各自建立 |
| 间隙锁(普通范围扫描) | 有 | 无 |
| 不匹配行的记录锁释放 | 事务结束才放 | 评估后立即释放 |
| 死锁概率 | 相对高 | 明显降低但仍有 |
代价要写明白:READ COMMITTED 只支持行格式 binlog,设成 MIXED 服务端会自动切回行格式。另外纯粹为了解决死锁而降级,往往治标——根因通常是 SQL 走的路径和访问顺序。
死锁是怎么被判出来的
InnoDB 会自动检测死锁,回滚它认为代价较小的那个事务,被踢的一方拿到 ERROR 1213。要看清楚发生了什么:
SHOW ENGINE INNODB STATUS\G
重点看 LATEST DETECTED DEADLOCK 段,每个事务会列出它持有的锁和在等的锁。锁模式那几行是线索:
| 模式字串 | 含义 |
|---|---|
| locks rec but not gap | 纯记录锁,没有间隙部分 |
| 带 supremum 伪记录行的 next-key 锁 | 延伸到索引末端的 next-key 锁 |
| insert intention waiting | 插入意向锁在等别人的间隙锁 |
想把每一次死锁都记进错误日志而不是只看最后一次,开这个:
SET GLOBAL innodb_print_all_deadlocks = ON;
乐观锁怎么写才算数
目录站条目编辑这种「读得早、写得晚」的场景,悲观锁会把编辑页挂锁挂到用户点保存,不合适。更常见的做法是版本号:
UPDATE items
SET intro = ?, version = version + 1, updated_at = NOW()
WHERE id = ? AND version = ?;
三条要点缺一不可:
第一,必须检查受影响行数。返回 0 说明期间有人改过,这时要重新读取再提示用户,而不是默默当作成功。
第二,读写要用同一个版本号,前端表单里带上原 version,别在提交时现查。
第三,更新时间和版本同时推进,方便排查——谁在什么时候抢先一步,日志里看得出来。
批量任务那边的另一套思路
巡检任务几千条批量更新,如果每条都走「先查再 UPDATE」,一次任务就是几千次往返,还会和人工编辑抢锁。更稳的做法:
一是固定顺序,按主键升序处理,减少互相等待成环的机会。
二是拆小批次,每批几百条,批间留间隔,失败时重跑那一批就行,不用整批回滚。
三是能用单条 UPDATE 完成的,不要用读改写。比如计数类字段直接 SET n = n + 1,数据库自己保证原子。
自查清单
把 UPDATE 和 DELETE 的 WHERE 条件逐个对着索引查一遍执行计划;确认有没有走全表扫描;高危写入是否在同一个事务里既做锁定读又做普通 SELECT(官方明确不建议在 RR 下混用这两类语句,因为两者看到的数据状态不同);以及最重要的——给每条会长期跑的写入语句一个「谁改了同一行」的可观测入口,问题出现时能定位到人而不是猜。