innodb没有锁升级机制,但会因全表扫描、显式表锁、ddl操作或间隙锁等产生“事实上的锁升级效果”;关键在where条件是否走索引,否则即使只更新一行也会锁大量行或页。

InnoDB 没有传统意义上的“锁升级机制”——它不会像 SQL Server 那样,当行锁数量超过阈值就自动把 1000 个行锁合并成 1 个表锁。这是根本性差异,不是实现细节不同,而是设计哲学不同。
InnoDB 不做锁升级,但会出现“事实上的锁升级效果”
这听起来矛盾,实际很常见:你写了一条 UPDATE,本意只改 3 行,结果整张表被卡住。这不是引擎“升级”了锁,而是你触发了它不得不扩大锁定范围的条件:
-
WHERE条件没走索引 →InnoDB扫描全表,对每个扫描到的页加意向排他锁(IX),同时对所有满足条件的行加X锁;但因为没索引定位,它得先读完所有行才能判断哪些匹配,过程中大量页锁+行锁累积,效果等同于表锁 - 使用
LOCK TABLES ... WRITE→ 直接加真实表级锁,和引擎无关,是显式指令 - 执行
ALTER TABLE→ DDL 操作强制申请元数据锁(MDL)并等待所有活跃事务释放行锁,期间新请求全阻塞 - 在
REPEATABLE READ隔离级别下执行范围SELECT ... FOR UPDATE→ 触发间隙锁(Gap Lock)或临键锁(Next-Key Lock),锁住索引区间而非单行,插入新记录被拦住,看起来像“锁住了空隙”
UPDATE / DELETE 为什么容易“锁表”?
关键不在语句本身,而在执行计划是否走索引:
- 有主键或二级索引且
WHERE能命中 → 锁粒度精准到行(或加少量间隙锁) -
WHERE用了函数、类型隐式转换、OR多条件未全部覆盖索引 → 优化器放弃索引,走全表扫描 → 锁住所有扫描页上的行(哪怕最终只更新 1 行) - 索引区分度极低(如性别字段建了索引),优化器估算走索引比全表还慢 → 选择全表扫描 → 同样导致大面积加锁
示例:UPDATE users SET status = 1 WHERE phone = '138...';
如果 phone 字段没索引,或索引是 (province, phone) 但查询没带 province,就会全表扫。
READ COMMITTED 能缓解“伪锁升级”,但有代价
切换隔离级别到 READ COMMITTED 后:
- 间隙锁关闭 → 不再锁范围,只锁命中的具体行
- 语句执行完立刻释放不匹配行的锁 → 不会像
REPEATABLE READ那样锁到事务结束 - 但幻读风险回归,业务逻辑需自行兜底(比如应用层加唯一约束或重试)
这个调整对高并发写场景常立竿见影,但前提是业务能接受 RC 级别的语义。
真正难缠的从来不是锁本身,而是那些你以为“只改一行”、实则触发全表扫描的 SQL —— 它们藏在慢查询日志里,却在高峰期突然让整个订单表不可写。











