update不加where时innodb实际锁所有被扫描的聚簇索引记录及其间隙,即全表主键行加x锁+gap锁,rr下升级为next-key锁;索引失效导致全表扫描,等效“逻辑锁全表”;limit不减少锁范围;sql_safe_updates=1仅校验语法,不保证索引生效。

UPDATE不加WHERE时InnoDB实际锁什么
它不直接“锁全表”,而是锁住所有被扫描的聚簇索引记录(即主键行),外加这些记录之间的间隙。因为没WHERE,优化器只能走type = ALL全表扫描,InnoDB对每一条送入Server层判断的主键记录都加X锁+Gap锁——哪怕最终没更新任何一行,所有主键也被锁了。
可重复读(RR)隔离级别下,这种行为会进一步升级为next-key锁,覆盖值本身和后续间隙,导致其他事务无法插入、更新或查询相邻范围的数据。
典型信号是执行SHOW ENGINE INNODB STATUS后看到trx_rows_locked接近表总行数。
为什么索引失效等于“逻辑锁全表”
InnoDB的行锁只加在索引上,不是加在“数据行”上。当WHERE city = 'Beijing'没有city索引,或虽有索引但因以下原因失效时,引擎无法跳过无关行:
-
WHERE user_id = '123'(user_id是INT,传字符串触发隐式类型转换) -
WHERE DATE(created_at) = '2026-05-01'(函数包裹索引列) -
WHERE status = 'pending' OR is_deleted = 1(OR破坏索引选择性) - 字符集不匹配,如utf8mb4列与utf8字符串比较
只要EXPLAIN显示key为NULL、type为ALL,就说明扫描不可控,锁范围已失控。
LIMIT不能缓解锁问题
UPDATE t SET status=1 WHERE status=0 LIMIT 1000看起来安全,其实危险:LIMIT只限制修改行数,InnoDB仍需扫描并加锁所有满足status=0条件的行——如果该条件没索引,就是锁全表再挑1000行改。
真正可控的分批方式是按主键范围切片:UPDATE t SET status=1 WHERE status=0 AND id BETWEEN 10000 AND 19999,前提是id是主键或有索引。
别信“小表无所谓”——哪怕只有1万行,全表扫描+加锁也会阻塞其他事务几十毫秒以上,在高并发场景足够引发雪崩。
sql_safe_updates=1不是保险柜,只是第一道卡口
开启sql_safe_updates=1后,MySQL会拒绝两类语句:UPDATE无WHERE,或WHERE中不含任何索引字段。但它不校验索引是否真的生效——比如你写了WHERE phone = '138xxx',而phone字段建了索引但用了LIKE '%xxx',索引失效,sql_safe_updates照样放行。
所以必须配合EXPLAIN验证执行计划,且每次上线前用等价SELECT语句预估扫描行数。最易被忽略的是:UPDATE影响行数 ≠ 扫描行数,后者才是锁的根源。











