union默认去重是性能断崖的根源,因其强制distinct导致每个子查询结果写入临时表并全量排序去重,易触发磁盘临时表和i/o飙升;真正有效的解法是重写为join、物化为派生表或用or/case替代。

UNION默认去重是性能断崖的根源
UNION不是简单拼接,它强制执行DISTINCT——每个子查询结果都要写入临时表,再全量排序+去重。107行“祖传SQL”里嵌套的UNION,可能触发多次磁盘临时表(tmp_table_size不足时),I/O直接拉垮。登录态变慢,往往是因为用户上下文引入了更多过滤条件,让原本能走索引的子查询退化为全表扫描,叠加去重开销,5秒响应就毫不意外。
UNION ALL不等于“安全”,只是去掉去重而已
换成UNION ALL能跳过排序和去重,但没解决根本问题:每个子查询仍独立执行、仍可能全表扫、仍可能生成大结果集再合并。常见误区是以为换掉UNION就万事大吉,其实:
- 若子查询里有
SELECT *,网络和内存压力照旧; - 若
WHERE条件没下推到各子查询内部,外部过滤会失效; - 若合并后还要
ORDER BY或LIMIT,MySQL仍得把全部结果装进临时表再处理。
子查询里套UNION,等于双重嵌套开销
当UNION出现在子查询中(比如WHERE x IN (SELECT ... UNION SELECT ...)),MySQL 5.7及更早版本大概率走嵌套循环:外层每行都触发一次完整的UNION执行。测试显示,百万级数据下,这种结构比等价的JOIN慢20倍以上。真正有效的解法不是调优UNION,而是重写:
- 用
JOIN替代IN子查询(尤其NOT IN必须转LEFT JOIN ... IS NULL); - 把UNION逻辑提前物化为派生表,再与主表连接;
- 确认业务是否真需要UNION语义——有时
OR条件或CASE WHEN就能覆盖。
EXPLAIN看不到的坑:临时表落地和索引失效
EXPLAIN只告诉你用了什么索引,不告诉你临时表是否落到磁盘。关键要看Extra列:
- 出现
Using temporary+Using filesort,基本确定性能瓶颈在此; - 子查询里的
ORDER BY或GROUP BY若没命中索引,会额外触发一次排序; - 跨库或跨引擎(如InnoDB + MyISAM)的UNION,索引无法复用,只能各自扫表。
真正卡住的,往往是那些EXPLAIN看起来“还行”,但实际执行时因数据分布突变、统计信息陈旧或参数配置不当,导致临时表撑爆内存的瞬间。











