union不触发索引合并,其性能瓶颈在于临时结果集处理与重复扫描;应通过预聚合、汇总表或物化json等方式前置计算。

UNION本身不触发索引合并(Index Merge)
这是最容易混淆的点:UNION 是 SQL 层面的结果集合并操作,而 MySQL 的 index_merge 是查询执行器在单表多条件扫描时的**内部优化策略**,两者完全无关。你在 EXPLAIN 中看到 type: index_merge,只可能出现在类似 WHERE a = 1 OR b = 2 这种单表多索引条件的场景里,绝不会因为写了多个 SELECT ... UNION SELECT ... 就自动启用索引合并。
多重UNION慢的真正瓶颈在临时结果集处理
每个 SELECT 子句独立执行,各自走自己的执行计划——哪怕每张表都有 idx_year,MySQL 也不会跨子句复用或协调索引访问。问题出在合并阶段:
- 默认
UNION强制去重:会把所有子查询结果全拉到内存(或磁盘临时表),再排序 + 去重,数据量一过百万,Using temporary; Using filesort就必然出现 -
UNION ALL虽跳过去重,但若各子句都无索引、或GROUP BY/ORDER BY放在外部,仍会触发临时表和排序 - 子句间无法共享中间结果:比如 6 个省表都按
year分组统计,MySQL 不会提前物化这个分组逻辑,而是重复执行 6 次全表扫描 + 分组
为什么加索引后效果仍有限?
索引只加速单个 SELECT 的数据定位,但解决不了结构性开销:
- 即使每张表都有
KEY idx_year (year),SELECT year, COUNT(*) FROM t GROUP BY year仍需遍历该索引全部叶子节点(不是跳过不相关年份) - 多个
UNION ALL合并后,外部GROUP BY y仍要对全部拼接结果重新哈希分组,索引在这一层完全失效 - 如果表数据量差异极大(如一张千万级、五张十万级),优化器可能为小表选错执行计划,进一步拖慢整体
绕过UNION低效的实操路径
别在 SQL 层硬扛合并逻辑,把“多次扫描 → 一次聚合”这件事提前做掉:
- 建汇总表:每天/每次补数后,跑
INSERT INTO summary_table SELECT 'guangdong' AS province, year, COUNT(*) c FROM guangdong_data_queries GROUP BY year,之后查就只需单表聚合 - 用视图封装预聚合:创建视图
CREATE VIEW yearly_summary AS SELECT 'guangdong' p, year, COUNT(*) c FROM guangdong_data_queries GROUP BY year UNION ALL ...,然后SELECT year, SUM(c) FROM yearly_summary GROUP BY year—— 至少避免重复写 SQL - 极端情况直接物化 JSON:把每个指标的历年统计压成
JSON_OBJECT('2022', 800, '2023', 900, ...)存一列,模糊查时只解构必要年份,IO 和 CPU 都大幅下降
最常被忽略的一点:UNION 的性能问题从来不在语法本身,而在它掩盖了「本该提前聚合」的数据使用模式。一旦表数量超过 3 个、单表行数超百万,就该放弃拼 SQL,转向前置计算。











