UPDATE范围条件未走索引会锁全表,因InnoDB需全表扫描并对每行加记录锁,等效表锁;EXPLAIN显示key为NULL、type=ALL即证实,应建对应索引或联合索引。

UPDATE范围条件没走索引,直接锁全表
WHERE 条件没命中索引时,InnoDB 无法定位具体行,只能全表扫描并为每行加记录锁(或升级为表级意向锁),这会让锁粒度失控。两个并发 UPDATE 都走全表扫描,就相当于在抢同一把“伪表锁”,极易触发死锁。
常见错误写法:UPDATE orders SET status = 'done' WHERE created_at > '2026-10-01',而 created_at 字段无索引。
- 验证方式:对语句执行
EXPLAIN FORMAT=TRADITIONAL,若key为NULL、type是ALL、rows接近表总行数,说明没走索引 - 修复动作:给
created_at加单列索引,或与查询高频字段组合成复合索引(如(status, created_at)) - 注意:即使有索引,若 WHERE 中用了函数(如
DATE(created_at))或隐式类型转换,也会导致索引失效
ORDER BY + LIMIT 在范围UPDATE中不保证加锁顺序
很多人以为加 ORDER BY id LIMIT 100 就能统一加锁顺序,但实际无效——因为 LIMIT 是在 WHERE 筛选后才生效,而加锁发生在扫描匹配行的过程中。如果 WHERE 条件本身不走索引,扫描顺序不可控,ORDER BY 就只是最后排序结果,不影响锁的获取路径。
更危险的是:高并发下多个事务用相同 LIMIT 分页 UPDATE 同一范围,可能因数据被其他事务修改导致分页偏移,重复处理或漏处理,间接放大死锁概率。
- 真正起作用的是 WHERE 条件是否通过索引确定扫描顺序,比如
WHERE id BETWEEN 1000 AND 2000 ORDER BY id,前提是id是主键或有索引 - 避免用
LIMIT做分批更新的“安全假象”,改用游标式:记录上一批最大id,下一批查WHERE id > ? AND ... - 务必用
EXPLAIN确认Extra不含Using filesort,否则ORDER BY没参与执行计划
REPEATABLE READ 下间隙锁引发的隐性冲突
MySQL 默认隔离级别是 REPEATABLE READ,对范围条件(如 WHERE id > 100)会自动加 Next-Key Lock(记录锁 + 间隙锁)。这意味着不仅锁住现有匹配行,还锁住“下一个不存在的值”之间的空隙。多个事务同时尝试在相邻间隙插入或更新,就会卡在同一个 Gap 上。
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
典型现象:两个事务都执行 UPDATE t SET x = 1 WHERE id > 500 AND id ,即使表里当前只有 id=501 和 id=599 两行,它们也会各自锁住 (500,501)、(501,599)、(599,600) 这些间隙,后续 INSERT 或 UPDATE 可能因插入意向锁与间隙锁冲突而死锁。
- 快速验证:执行
SELECT * FROM t WHERE id > 500 AND id ,再开另一个会话尝试INSERT INTO t (id) VALUES (550),看是否被阻塞 - 缓解手段:若业务允许幻读,可临时降级隔离级别:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED,此时只加记录锁,不加间隙锁 - 更稳妥做法:用唯一条件替代范围,比如拆成
WHERE id IN (501,502,...)并确保该字段有唯一索引
多事务交叉更新同一组行,顺序相反
这是最典型的死锁根因,和并发量大小无关——哪怕每秒只跑两条 UPDATE,只要两个事务以相反顺序锁定同一组主键值,就会立刻形成循环等待。
例如事务 A 执行 UPDATE t SET v=1 WHERE id IN (101, 105, 109),优化器按主键索引顺序锁 101→105→109;事务 B 同时执行 UPDATE t SET v=2 WHERE id IN (109, 101),却因执行计划不同(比如走了二级索引)先锁 109→101。结果 A 持 101 等 105,B 持 109 等 101,A 再去锁 109 时发现被 B 占着,B 去锁 105 时又被 A 占着。
- 根本解法不是加索引,而是强制所有同类 UPDATE 使用完全一致的条件表达式和参数顺序(ORM 场景尤其要注意生成 SQL 的确定性)
- 批量操作不要拼
IN (?)动态列表,改用临时表或服务端分片,确保每次 UPDATE 的id列表严格升序 - 线上已发死锁?立刻查
SHOW ENGINE INNODB STATUS,重点关注 “LATEST DETECTED DEADLOCK” 下的*** (1) WAITING FOR THIS LOCK TO BE GRANTED:和*** (2) HOLDS THE LOCK(S):对应的行号和索引名
真实场景里最难缠的不是锁本身,而是你以为控制了顺序,其实执行计划早就不听使唤了——EXPLAIN 必须每条 UPDATE 都跑,不能靠“应该走了索引”这种猜测。










