mysql执行update语句不会默认锁全表,但where条件未走索引时会全表扫描并为每条聚簇索引记录加next-key锁,等效锁表;根本原因是innodb行锁依赖索引定位,无索引则逐行加锁。

MySQL执行UPDATE语句不会“默认锁全表”,但只要WHERE条件没走索引,或者走了索引却因优化器放弃、隐式转换、高重复值等原因退化为全表扫描,InnoDB就会对所有扫描行加next-key lock——效果等同于锁表。
WHERE字段没索引或索引失效时,UPDATE会扫全表加锁
这是最常见也最危险的情况。InnoDB的行锁依赖索引定位,没有可用索引,就只能从聚簇索引头开始逐行判断,每扫描一行就加一把next-key lock(记录锁 + 间隙锁)。哪怕只更新1行,也可能锁住几万行。
-
EXPLAIN中key为NULL或type = ALL,基本可断定没走索引 - 常见失效场景:
WHERE DATE(created_at) = '2026-05-01'(函数操作)、WHERE phone = 138xxxx(phone是VARCHAR却传数字,触发隐式转换) - 即使建了索引,若该字段重复值极高(如
status只有'pending'/'done'),优化器可能直接跳过索引,改用全表扫描
非唯一索引导致回表+双层加锁,范围扩大
用非唯一二级索引(比如KEY idx_user_id (user_id))做WHERE条件时,InnoDB必须先锁二级索引项,再回表锁对应主键行——这两步非原子,且在REPEATABLE READ下还会额外加间隙锁。
- 锁不止1把:至少2处(二级索引 + 聚簇索引),还可能带间隙覆盖
- 死锁高发:事务A锁了
idx_user_id正等主键,事务B已锁主键正等idx_user_id - 复合索引中只要最左前缀是非唯一的(如
KEY (user_id, item_id)),且查询只用了user_id,同样触发此机制
事务未提交让锁“活”得比SQL执行时间长得多
UPDATE加的锁不是语句结束就释放,而是绑定整个事务生命周期。哪怕UPDATE本身毫秒完成,只要事务没COMMIT或ROLLBACK,锁就一直挂着,还可能因间隙锁扩散影响插入。
- ORM框架里混入HTTP调用、日志写入等耗时操作,极易拖长事务
- 连接池复用时,上一个请求忘了commit,下一个请求复用同一连接,锁直接继承
- 查长事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TRX_STATE = 'RUNNING',重点关注TRX_STARTED
如何快速验证和绕过锁表风险
别猜,用工具看真实行为。EXPLAIN只告诉你“怎么查”,而锁状态得靠实时观测。
- 强制走索引:
UPDATE t FORCE INDEX (idx_mark_id) SET status = 'done' WHERE mark_id = 123,但不能解决高重复值问题 - 分批更新:先
SELECT id FROM t WHERE mark_id = 123 ORDER BY id LIMIT 1000,再UPDATE t SET ... WHERE id IN (1,2,...) - 验证锁范围:事务A执行
BEGIN; UPDATE t SET x=1 WHERE id=10;不提交 → 事务B执行INSERT INTO t (id) VALUES (9);,若被阻塞,说明间隙锁已生效 - 开启安全模式:
SET sql_safe_updates = 1,避免无WHERE或无索引的UPDATE被执行
真正难处理的不是“锁哪几行”,而是“为什么明明有索引,优化器却不用”。它取决于统计信息、数据分布、参数配置,甚至MySQL版本。上线前务必用EXPLAIN FORMAT=JSON确认used_key和key_length,而不是只看有没有建索引。











