无WHERE条件的UPDATE会锁全表,根本原因是InnoDB必须扫描并加锁每一行聚簇索引记录;Server层无法过滤,引擎层遍历主键B+树所有叶子节点,对每条记录加X锁和间隙锁,等效锁表。

无WHERE条件的UPDATE会锁全表,根本原因不是MySQL“故意锁表”,而是InnoDB必须扫描并加锁每一行聚簇索引记录。
为什么没WHERE就等于全表扫描加锁
UPDATE不带WHERE时,Server层无法过滤任何行,引擎层只能遍历整张表的聚簇索引(即主键B+树的所有叶子节点),对每一条记录都加X锁(记录锁);在可重复读(RR)隔离级别下,还会额外加间隙锁(Gap Lock),把所有主键间隙也锁死。最终效果是:所有主键值 + 所有间隙都被锁定,其他事务的INSERT、UPDATE、SELECT FOR UPDATE全部被阻塞。
- 哪怕表只有10行,也会加10个记录锁 + 11个间隙锁
- SHOW ENGINE INNODB STATUS中
trx_rows_locked会接近表总行数 - 执行
EXPLAIN UPDATE users SET status = 'pending'会显示type=ALL、key=NULL
写了WHERE也不安全:索引失效照样锁全表
很多线上事故不是真没写WHERE,而是WHERE字段没走索引——结果和没写一样。常见失效场景包括:
-
WHERE user_id = '123'(user_id是INT,传入字符串触发隐式转换) -
WHERE DATE(created_at) = '2026-08-01'(函数导致索引无法命中) -
WHERE status = 'done' OR is_deleted = 1(OR破坏索引选择性,优化器弃用索引) - 联合索引
(a, b),但查询只用WHERE b = 1(不满足最左前缀)
这些情况都会让EXPLAIN返回type=ALL或type=index,rows远超预期匹配数。
sql_safe_updates能防住吗
开启sql_safe_updates后,MySQL会拒绝执行不带有效约束的UPDATE/DELETE,但它只检查语法层面是否含键列或LIMIT,并不验证索引是否真被使用。也就是说:
- 它能拦住
UPDATE users SET name = 'x'这种裸语句 - 但拦不住
UPDATE users SET name = 'x' WHERE mark_id = 123——哪怕mark_id没索引或索引失效 - 客户端需显式设置
SET sql_safe_updates = 1,服务端配置safe-updates才生效
真正有效的验证和规避方式
上线前必须做三件事,缺一不可:
- 跑
EXPLAIN FORMAT=TREE或EXPLAIN FORMAT=JSON,确认used_key非空、key_length合理、rows与实际匹配量接近 - 用
SHOW INDEX FROM users核对索引是否存在、字段顺序是否匹配查询模式(如WHERE含a和b,索引应为(a, b)而非(b, a)) - 高并发批量更新时,放弃
WHERE mark_id = ?直改,改用主键分段:WHERE id BETWEEN ? AND ?,确保每次扫描范围可控且边界确定
最常被忽略的一点是:锁持续时间不取决于SQL执行快慢,而取决于事务何时提交。一个毫秒级的UPDATE,如果裹在长达几秒的HTTP调用里,锁就挂几秒——间隙锁会一直封锁插入点,阻塞新数据写入。

















