mysql 5.7 的 in 子查询性能差因默认嵌套循环且不物化,8.0 需满足配置、索引、类型匹配及统计信息准确等条件才能启用物化与索引下推。

MySQL 5.7 的 IN 子句索引选择不稳定,核心原因是它不评估子查询实际代价,而 8.0 引入了基于成本的物化决策和索引下推能力——但前提是配置、索引结构和查询写法都得对上。
5.7 的 IN 子查询默认走嵌套循环,每行重执行
只要 IN 后跟的是相关子查询(即引用外层表字段),5.7 就几乎必然标记为 DEPENDENT SUBQUERY,执行时外层每返回一行,就完整执行一次子查询。哪怕子查询本身只查几千行,外层 10 万行就会触发 10 万次解析+执行+全表扫描。
- 典型表现:
EXPLAIN中select_type列反复出现DEPENDENT SUBQUERY,且rows值不是单个估算值,而是随外层行数波动 - 即使子查询带
WHERE条件,若字段无索引或顺序不对,5.7 也不会下推过滤,全量扫描后再内存过滤 -
NOT IN更危险:会额外触发临时表物化,且对NULL值处理逻辑复杂,极易退化为全表扫
8.0 的物化机制不是自动生效,得看条件是否满足
8.0 默认开启 subquery_materialization_cost_based=ON,但是否真物化,取决于优化器对成本的判断——它可能觉得“逐行查更快”,尤其当统计信息陈旧或子查询结果集预估很小的时候。
- 必须确认是否启用:
SELECT @@optimizer_switch LIKE '%subquery_materialization_cost_based=on%',返回 1 才算有效 - 子查询中不能含非确定性函数(如
NOW()、RAND()),否则强制禁用物化 - 若子查询被改写为
JOIN更优,优化器可能跳过物化直接走连接路径——这不算 bug,是 Cost Model 的正常权衡
索引下推(ICP)在 IN 场景下极易失效
很多人以为升级到 8.0 就能自动加速 IN 子查询,其实 ICP 是否生效,高度依赖索引定义与查询条件的字面一致性。
- 检查信号只有 1 个:
EXPLAIN的Extra列是否出现Using index condition;没有它,说明条件仍在 Server 层过滤,没推到引擎层 - 子查询字段类型必须严格匹配:比如外层
orders.user_id是BIGINT UNSIGNED,内层users.id是INT,ICP 直接跳过 - 索引必须覆盖子查询的全部
WHERE字段,例如SELECT id FROM users WHERE status = 'active' AND city = 'sh',需建(status, city, id)联合索引,缺一不可 - 任何函数包装都会断链:
WHERE DATE(created_at) = '2026-05-01'→ 索引失效 → ICP 失效
升级后执行计划变差?先跑 ANALYZE TABLE
8.0 的 Cost Model 高度依赖统计信息准确性。刚升级时,旧表的统计信息仍是 5.7 时代生成的,分布直方图过时、行数估算偏差大,Cost Model 就会选错路径——比如该走子查询物化的,却选了嵌套循环;该用联合索引的,却选了单列索引。
- 立即执行:
ANALYZE TABLE users, orders;(尤其大表) - 验证是否更新:
SELECT table_name, rows, avg_frequency FROM information_schema.TABLES WHERE table_schema = 'your_db'; - 若仍不准,可手动设置采样率:
ANALYZE TABLE users UPDATE HISTOGRAM ON status, city WITH 16 BUCKETS;
真正容易被忽略的是:8.0 的优化能力不是“开关式”的,它是一整套联动机制——optimizer_switch 设置、索引结构、字段类型、统计信息、甚至查询里多一个空格,都可能让物化或 ICP 失效。别只盯着版本号,要盯住 EXPLAIN 输出里的每一个信号。











