
直接调大 range_optimizer_max_mem_size 是最快速见效的手段,但不是万能解药——它只解决“优化器因内存不足而放弃范围扫描、退化为全表扫描”这一特定问题,对解析开销、网络包大小、类型隐式转换等其他瓶颈无效。
为什么 IN 查询会突然变慢,且 EXPLAIN 显示 type=ALL
MySQL 8.0 的范围优化器在评估 IN 列表时,会尝试构建一棵“范围区间树”来高效定位主键或索引值。当列表长度达数千甚至上万时,这棵树所需内存可能超过默认的 range_optimizer_max_mem_size=8M。一旦超限,优化器就放弃该策略,回退到全表扫描(type=ALL),哪怕字段明明有主键索引。
典型现象包括:
-
EXPLAIN中key字段为空、rows高达几十万 - 查询执行时
Handler_read_rnd_next指标飙升 - MySQL CPU 使用率突增,其他查询排队等待
如何安全地调大 range_optimizer_max_mem_size
这不是一个“越大越好”的参数:它控制的是**单条查询**在范围优化阶段可使用的最大内存量,全局生效,且不区分会话。盲目设为 128M 可能导致高并发下内存耗尽。
推荐操作步骤:
- 先确认当前值:
SELECT @@range_optimizer_max_mem_size;(单位字节,默认 8388608) - 根据 IN 列表平均长度做保守估算:每 1000 个整型值约需 1–1.5MB,8000 个值建议设为
24M(即 25165824) - 在线动态修改(无需重启):
SET GLOBAL range_optimizer_max_mem_size = 25165824; - 写入配置文件持久化(
/etc/my.cnf或/etc/mysql/mysql.conf.d/mysqld.cnf):[mysqld] range_optimizer_max_mem_size = 24M
- 验证是否生效:
SELECT @@global.range_optimizer_max_mem_size;
调完之后还是慢?这些坑比参数更重要
即使 range_optimizer_max_mem_size 调得再高,以下问题仍会让 IN 查询失效:
-
max_allowed_packet不够:若 IN 列表拼成的 SQL 超过该值(默认 4M),连接直接断开,报错Packet for query is too large。必须同步调大,例如设为64M - 字段类型不一致:比如主表
id是BIGINT,但 IN 里混入了字符串'123',触发隐式转换,索引完全失效 - 字符集/排序规则不匹配:
VARCHAR字段用utf8mb4_0900_as_cs,而传入值是小写,但比较时大小写敏感,结果为空 - 应用层拼接 SQL:用
String.join()拼出含 8000 个值的 SQL,不仅慢,还极易被注入;应改用预编译参数或临时表
真正稳定的方案从来不是靠调一个参数硬扛,而是把“长 IN”从语法层面移除——用临时表 + JOIN 替代,才能绕过所有解析、内存、传输的软硬限制。参数调整只是争取排查时间的临时止血带,不是根治手段。











