union强制触发using temporary和using filesort,因其隐式等价于select distinct,需将全部子查询结果合并后排序去重,完全忽略子查询原有索引顺序,且逐行逐列比对去重,导致全量扫描、内存/磁盘临时表及i/o开销激增。

UNION 为什么强制触发 Using temporary 和 Using filesort
因为 UNION 隐式等价于 SELECT DISTINCT,MySQL 必须在合并后对全部结果做去重,而默认去重方式就是排序+扫描。只要执行计划里出现 Using temporary 或 Using filesort,就说明它正在建临时表、加载所有行、再按所有列排序——这个过程完全绕过原表索引的有序性,也压根不复用子查询中已有的索引顺序。
常见错误现象:EXPLAIN 显示两个子查询明明都走了索引,但 UNION 后整体 Extra 栏却同时出现这两个标记;或者 SHOW STATUS LIKE 'Created_tmp%' 中 Created_tmp_disk_tables 突增。
- 即使每个子查询结果本身已按某列有序(比如都有
ORDER BY id),UNION 仍会丢弃该顺序,重新全量排序 - 如果子查询返回列含
TEXT、BLOB或总宽度超max_allowed_packet,临时表立刻落盘,I/O 和锁压力翻倍 - 去重不是按主键或业务唯一键,而是对所有 SELECT 出来的列逐字节比对,字段越多、越长,CPU 消耗越高
UNION ALL 不影响索引使用,但也不“自动合并索引”
网上有些文章说 “UNION ALL 会自动合并索引”,这是误导。MySQL 对每个子查询独立走执行计划,table1 上的 idx_a_b 和 table2 上的 idx_a_b 互不影响,也不会被“合并”成一个新索引。所谓“索引生效”,只是指每个子查询能各自用上自己表上的索引。
真正容易被忽略的点:如果两个子查询对应列类型不一致(比如一边是 VARCHAR(50),另一边是 TEXT),MySQL 会隐式转换,导致其中一个子查询索引失效——这种问题在 UNION ALL 和 UNION 中都会发生,但 UNION 的额外开销会把这个问题掩盖得更久。
- 用
SHOW WARNINGS查是否有Truncated incorrect类提示 - 显式用
CAST(col AS CHAR)统一类型,比依赖隐式转换更可控 - 别只看
EXPLAIN有没有key字段,要确认rows和Extra是否合理
用 UNION 替代 OR 时,索引压力反而可能降低?
这是少数 UNION 能缓解索引压力的场景:当 WHERE 条件含 OR 且两边字段都有独立索引时(如 WHERE a = 1 OR b = 2),MySQL 可能无法有效使用索引合并(index merge),转而走全表扫描;但拆成 (SELECT ... WHERE a = 1) UNION ALL (SELECT ... WHERE b = 2) 后,每个分支都能精准命中对应索引。
注意前提是:两个子查询结果集天然无交集(比如 a 和 b 属于不同业务维度),否则你得自己确认是否允许重复——这时用 UNION ALL 才真省事,用 UNION 反而多了一层无谓去重。
- 必须保证各子查询 SELECT 的列数、类型、顺序完全一致,否则报错
ERROR 1222 -
LIMIT不能只写在外层,否则先合并百万行再截断;应分别加在每个子查询里 - 若原查询含
GROUP BY,不能简单拆分,需在子查询内先聚合,外层再UNION ALL,代价更高
真正拖慢的从来不是 UNION 关键字本身
如果你换了 UNION ALL 之后还是慢,大概率瓶颈压根不在合并逻辑,而在子查询内部:没索引的 JOIN、SELECT * 导致回表严重、WHERE 条件没下推、或者字段类型隐式转换让索引失效。UNION/UNION ALL 只是最后一道“工序”,前面任何一个子查询扫了全表,后面加再多 ALL 也没用。
最有效的排查路径是:对每个子查询单独 EXPLAIN,关掉缓存(SQL_NO_CACHE),看 type 是不是 ref 或 range,rows 是否在预期量级,Extra 有没有 Using where; Using index 这种理想组合。
- 别迷信 “用了 UNION ALL 就一定快”,它只是不添乱;快不快,取决于每个
SELECT写得够不够紧 - 临时表参数
tmp_table_size和max_heap_table_size调太大有内存风险,调太小又容易落盘——得结合实际结果集大小来估 - 如果 INSERT ... SELECT + UNION ALL 中途卡住,优先考虑拆成批次,而不是硬调参数











