bigywater.tech
MySQL / InnoDB · 57

MySQL 为什么没有锁升级

让一个事务真实锁住接近全表96%的行,看锁的数量、内存占用,以及剩下没被命中的行是否真的完全不受影响。

48023行
单个事务真实锁住的行数,约占全表96%
约90KB
对应消耗的真实锁内存,没有升级成表锁

做的事:一个事务真实锁住接近一半的表

orders表5万行,customer_id BETWEEN 1 AND 8000这个条件真实匹配了48023行(约96%)。

session KSTART TRANSACTION; SELECT COUNT(*) FROM orders WHERE customer_id BETWEEN 1 AND 8000 FOR UPDATE; SELECT SLEEP(5); COMMIT;

验证:真实数出来的锁记录数量,和消耗的真实内存

实测 · 本机SHOW ENGINE INNODB STATUS真实输出
637 lock struct(s), heap size 90232, 48023 row lock(s)

InnoDB真实为这48023行分别维护了独立的记录锁,用637个锁结构(每个结构用位图压缩了同一个数据页上的多条记录锁)、总共真实只占用约90KB内存就存下了。这不是一把笼统的"这张表被锁住了"的粗粒度标记,而是真实精确到每一行的细粒度记录,可以通过performance_schema.data_locks逐条查出具体锁在了哪一行。

验证:真实测试没被这个条件覆盖的行,完全不受影响

实测 · 本机执行输出(K的事务仍在持有锁期间)
更新customer_id=100(真实在K的锁定范围1~8000内):
  ERROR 1205 (HY000): Lock wait timeout exceeded   ← 真实被挡住

更新customer_id=19999(真实在K的锁定范围之外):
  真实538微秒(约0.54毫秒)就完成了,同一张表,完全不等待
如果MySQL真的有"锁升级"机制——像某些数据库那样,当一个事务锁住的行数超过某个阈值,就自动把它升级成一把粗粒度的表锁——那么customer_id=19999这一行,哪怕它压根不在K真实的查询条件里,也会被这把表锁真实误伤,一起被挡住。但本文真实测出来的结果是:K锁了接近5万行里的48023行,剩下不到2000行里随便挑一个,真实完全不受影响,0.5毫秒就能被另一个会话改完。InnoDB从设计上就没有锁升级这回事,行锁永远是行锁,代价是锁的数量会随着涉及的行数线性增长(这次是637个锁结构、90KB内存),换来的好处是"没有被真实条件命中的行"永远不会被无辜连坐。

分步演示:锁住接近整张表,未命中的行真实毫发无损

每一步都是本文上面用真实并发会话跑出来的原始结果。

锁了96%的表,剩下的4%真实完全不受影响
这一步新验证的行

    本文用到的工具

    • 本机 MySQL 26.7.0(InnoDB),5万行orders,一个事务真实锁住其中48023行
    • performance_schema.data_locks + SHOW ENGINE INNODB STATUS:真实统计锁结构数量和内存占用
    京ICP备2026067502号-1京公网安备11010502063487号