SELECT ... FOR UPDATE 锁范围需用主键或唯一索引控制,避免间隙锁;分批更新用原子条件+游标分页;一致性验证靠快照比对与binlog抽样。

大事务卡死数据库时,SELECT ... FOR UPDATE 锁范围怎么控?
锁住整张表或大量无关行,是拆分失败最常见原因。不是所有 SELECT ... FOR UPDATE 都安全——它默认在可重复读隔离级别下会加间隙锁(Gap Lock),哪怕你只查一个 id,也可能锁住整个索引区间。
- 务必在
WHERE条件中使用**主键或唯一索引字段**,避免走全表扫描或非唯一索引(后者易触发范围锁) - 拆分前先用
EXPLAIN SELECT ... FOR UPDATE看执行计划,确认key列显示实际使用的索引名 - 若业务允许,把事务隔离级别临时降为
READ COMMITTED(需确认 binlog 格式为ROW),能关闭间隙锁 - 别在循环里反复执行
SELECT ... FOR UPDATE——改用一次查出 ID 列表,再按批次UPDATE ... WHERE id IN (...)
分批 UPDATE 时,怎么避免漏更新或重复处理?
靠“查一批、改一批、删标记”这种三步法极易出错:中间失败会导致状态不一致;并发写入可能让同一批数据被多个 worker 拿到。
- 用原子性条件更新,例如:
UPDATE t SET status='done' WHERE status='pending' AND id BETWEEN 1000 AND 2000 LIMIT 100,返回影响行数判断是否真改了 - 加业务时间戳字段(如
process_at),首次处理时用SET process_at = NOW() WHERE process_at IS NULL AND ... LIMIT N,后续只查process_at IS NOT NULL的记录 - 避免用
OFFSET分页(LIMIT 10000,100),越往后越慢且易跳过数据;改用基于主键的游标分页:WHERE id > last_id ORDER BY id LIMIT 100
INSERT ... ON DUPLICATE KEY UPDATE 能否替代事务重试逻辑?
可以,但仅适用于“幂等写入”场景,比如统计类汇总、状态最终一致型更新。它本质是单条语句的原子操作,不参与外层事务锁竞争。
- 必须确保表有合适的唯一约束(如
UNIQUE KEY (order_id, type)),否则ON DUPLICATE KEY不触发 - 注意
VALUES(col)引用的是本次 INSERT 的值,不是原记录值;要保留旧值需显式写成col = VALUES(col) + old_col这类表达式 - 它无法回滚已发生的其他 DML 操作,所以不能作为大事务的通用替代,只能用于局部幂等子操作
拆分后如何验证数据一致性?
人工对账不可靠,脚本比对也容易因查询时机错过中间态。关键不是“全量校验”,而是守住变更边界和终态。
- 拆分前先记下关键聚合值快照,例如:
SELECT COUNT(*), SUM(amount) FROM t WHERE status='pending',存在临时表或配置中心 - 每批处理完立刻查该批 ID 范围的当前状态,用
SELECT COUNT(*), SUM(amount) FROM t WHERE id BETWEEN x AND y AND status='done'做局部验证 - 上线后开启 binlog 监听,用
mysqlbinlog抽样检查拆分期间的UPDATE语句是否都命中预期行,避免 where 条件写错导致误更新
真正难的不是拆动作本身,而是把“正在拆”的中间状态变成可观测、可中断、可回退的确定行为。很多人卡在没设计好断点续传的标记字段,或者把锁粒度和业务语义混在一起判断。


















