MySQL 5.7使用FOR UPDATE锁行时是否存在行数限制?
问题根因说明
InnoDB 不存在「行锁数量超过阈值就自动升级为表锁」的设计,你遇到的现象本质是优化器执行计划变更导致的:
当IN条件中的主键数量达到阈值后,MySQL优化器基于成本估算,认为全表扫描的成本比走主键索引批量查询的成本更低,因此放弃了主键索引,直接执行全表扫描。此时FOR UPDATE会对全表所有记录加锁,同时InnoDB的间隙锁规则还会阻塞新数据插入,和你观测到的表现完全一致。
该现象仅和IN条件内的ID数量相关,和ID值无关,刚好符合优化器成本计算的逻辑:IN内的等值条件越多,走索引的累加成本就越高,超过阈值后就会触发执行计划切换。
排查步骤
- 首先验证执行计划差异,分别对两个查询执行EXPLAIN:
EXPLAIN select * from mytable where id in (1, 2,...,13) for update; EXPLAIN select * from mytable where id in (1, 2,...,14) for update;
如果14个ID的查询结果中type字段为ALL,key字段为NULL,即可确认是优化器未走主键索引导致的全表扫描。
2. 检查相关参数配置:
执行SHOW VARIABLES LIKE 'eq_range_index_dive_limit';,该参数控制IN条件内的等值数量超过阈值后,优化器不再做实际索引采样统计行数,而是使用历史统计信息估算,很容易出现行数估算偏差,导致选错执行计划。MySQL 5.6及更早版本该参数默认值为10,5.7之后默认值调整为200,如果你的环境该参数值低于14,就会触发该问题。
3. 检查表统计信息是否准确:如果表数据有频繁变更但未及时更新统计信息,也会导致优化器成本计算错误。
解决方案
- 强制走主键索引:在查询中显式指定使用主键索引,避免优化器选错执行计划:
select * from mytable FORCE INDEX(PRIMARY) where id in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) for update;
- 调大参数阈值:如果是
eq_range_index_dive_limit参数值过低,将其调整为200,同时修改配置文件保证重启后生效:
SET GLOBAL eq_range_index_dive_limit = 200;
- 拆分查询:如果IN内的ID数量确实较多,可以拆分为多个分批查询,每个批次的ID数量控制在阈值以内。
- 更新表统计信息:执行
ANALYZE TABLE mytable;修正统计信息偏差,帮助优化器生成正确的执行计划。
内容的提问来源于stack exchange,提问作者mykola
相关产品推荐
相关产品推荐

