update未走索引会导致全表扫描并加记录锁+间隙锁,等效锁整表;需用explain验证执行计划,关注type(非all/index)、key(非null)和rows(接近预期更新行数)。

UPDATE语句没走索引,InnoDB就会全表扫描并逐行为每行加记录锁+间隙锁,效果等同于锁整张表——这不是配置问题,是执行计划失控的必然结果。
WHERE条件是否命中索引?用EXPLAIN当场验证
别猜,直接执行EXPLAIN看执行计划。重点盯三个字段:
-
type不能是ALL或index;理想值是const(主键等值)、ref(普通索引等值)或range(范围查询) -
key字段必须显示具体索引名,比如PRIMARY或idx_status;若为NULL,说明没用上索引 -
rows应接近你预期更新的行数;如果接近表总行数,锁范围已经失控
常见失效场景:WHERE DATE(created_at) = '2026-08-01' → 改成WHERE created_at >= '2026-08-01' AND created_at ;<code>WHERE user_id = '123'(user_id是INT)→ 改成WHERE user_id = 123。
Navicat里漏写WHERE或写成WHERE 1,等于主动锁表
这是最常被忽略的操作事故:在Navicat编辑器里手写UPDATE users SET status='done',没写WHERE,或误写成WHERE 1。MySQL无法过滤,只能全表扫描+加锁,哪怕只改1行,也锁全表。
- 强制自己写
UPDATE前先跑对应SELECT:比如SELECT id FROM users WHERE status='pending' LIMIT 5,确认结果集和索引是否生效 - Navicat连接设置里关掉自动提交:
SET AUTOCOMMIT = 0,误执行后还能ROLLBACK - 别信“加了
LIMIT 10就安全”——MySQL仍会锁住扫描路径上的所有行,不是最终更新的那10行
批量UPDATE必须按主键范围分批,禁用OFFSET
即使WHERE走了索引,一次性更新几千行也会持续持有大量X锁,引发锁等待甚至死锁。关键不是“有没有索引”,而是“单次事务锁多少行+多久”。
- 用
WHERE id BETWEEN ? AND ?切分,起始值取上一批的MAX(id),避免漏或重 - 单批控制在100–500行之间;若涉及多表JOIN或大字段,建议缩到100以内
- 每批后显式
COMMIT,并加SLEEP(0.01)缓解锁争抢 - 绝对不要用
LIMIT 100 OFFSET 1000——MySQL仍重复扫描前面的行,锁范围不可控
长事务让行锁变事实表级阻塞
锁本身是行级的,但事务不提交,锁就一直挂着。别人想改同一行就得排队,人一多,看起来就像“表被锁死”。这不是锁升级,是锁被占着不放。
- Navicat里执行完
UPDATE立刻COMMIT或ROLLBACK,别让它悬着 - 查谁卡住了:
SELECT * FROM information_schema.INNODB_TRX ORDER BY trx_started DESC LIMIT 5,重点关注trx_state为LOCK WAIT或运行超300秒的RUNNING事务 - 别直接
KILL;先确认该线程没被其他业务依赖,尤其看到trx_query是BEGIN或空值时,大概率是应用层没正确结束事务
真正危险的从来不是“会不会锁”,而是“锁的范围和时间是否可控”。索引、分批、事务边界——三者缺一不可,少一个,就可能从行锁滑向事实上的表级阻塞。











