union本身不触发索引合并,每个子查询独立执行且彼此隔离;索引是否生效取决于单个select内部写法;union后的order by/group by因无索引必然触发临时表和文件排序;真正瓶颈是结果集合并的结构性开销,而非索引缺失。

UNION本身不触发索引合并(Index Merge)
这是最常被误解的一点:你在EXPLAIN里看到type: index_merge,只可能出现在单表多条件查询中,比如WHERE a = 1 OR b = 2且a、b分别有独立索引。UNION是SQL层的结果集拼接操作,MySQL不会跨SELECT子句协调索引访问——哪怕6个子查询都查同一列、都带WHERE year = 2023,每个子句仍各自走自己的执行计划,彼此完全隔离。
每个子查询必须单独命中索引
索引生效与否,只取决于单个SELECT内部的写法和数据分布:
- 如果某个子查询写了
WHERE status + 1 = 2,哪怕status有索引,也会失效(表达式导致无法直接匹配B+树) - 复合条件如
WHERE province = 'guangdong' AND year = 2023,需要(province, year)联合索引才能高效定位;只建year单列索引,大概率退化为索引扫描而非范围查找 -
SELECT *容易引发回表,若业务只需id和name,建覆盖索引(province, year, id, name)能避免额外IO
UNION后的ORDER BY/GROUP BY让索引“失效”
即使每个子查询都跑得飞快,外部操作仍可能拖垮整体性能:
-
SELECT ... FROM t1 UNION ALL SELECT ... FROM t2 ORDER BY created_at:合并后的临时结果集无索引,ORDER BY必然触发Using filesort -
SELECT year, COUNT(*) FROM (SELECT year FROM t1 UNION ALL SELECT year FROM t2) AS u GROUP BY year:分组在内存/磁盘临时表上进行,原始表上的idx_year在此阶段完全不起作用 - 若各子表数据量差异极大(如一张千万级、五张十万级),优化器可能为小表选错执行计划(例如用
range扫描代替ref),进一步放大偏差
真正卡顿的不是索引,是临时结果集处理
UNION慢的根因从来不在“索引没建好”,而在于MySQL被迫把所有子查询结果先拉出来再统一处理:
-
UNION强制去重 → 必须建临时表 + 排序 + 比较 → 数据过百万就Using temporary; Using filesort -
UNION ALL虽跳过去重,但若子查询未加WHERE过滤,仍会把大量无效行灌入临时表 - 各子句间无法共享中间状态:6个省表按
year分组统计,MySQL不会复用一次分组逻辑,而是重复执行6次全索引扫描
绕开这个瓶颈的关键,是把“多次扫描 → 一次聚合”的动作从SQL层下移到应用或预计算层——比如建汇总表、用物化JSON存历年统计,而不是寄希望于索引能解决结构性开销。











