UNION 会丢索引,UNION ALL 才可能用索引;每个子查询需独立建合适组合索引,ORDER BY 和 LIMIT 必须写在各子句内,避免全表扫描与临时表落盘。

UNION 会丢掉索引,UNION ALL 才可能用上索引
MySQL 对 UNION 的默认行为是去重(隐式加 DISTINCT),这会导致它把所有子查询结果先写入临时表,再排序去重——整个过程绕过原表索引。而 UNION ALL 不去重,每个子查询可独立走索引。
- 只要业务逻辑允许重复(比如日志合并、分表查询无交集),必须优先选
UNION ALL -
UNION即使子查询各自有索引,最终执行计划里也常出现Using temporary; Using filesort - 用
EXPLAIN看执行计划时,重点盯type列:要是变成ALL或index,基本说明索引失效了
每个子查询得单独建对索引,不能指望“联合索引覆盖全部”
MySQL 不会对 UNION / UNION ALL 的多个分支做跨查询的索引合并。每个 SELECT 是独立优化的,所以索引得按子查询各自的 WHERE、ORDER BY 和 SELECT 字段分别设计。
- 例如:
SELECT id, name FROM t1 WHERE status=1 ORDER BY created_at LIMIT 10和SELECT id, name FROM t2 WHERE type='log' ORDER BY updated_at LIMIT 10,就得分别为t1(status, created_at, id, name)和t2(type, updated_at, id, name)建组合索引 - 别试图只给
t1(id, name)和t2(id, name)建索引——没WHERE条件字段,照样全表扫 - 如果某个子查询用了
LIKE '%abc'或函数如DATE(created_at),对应字段上的索引大概率失效,得提前重构条件
ORDER BY 和 LIMIT 必须写在每个子查询里,不能只写在最后
MySQL 不支持在 UNION 语句末尾统一加 ORDER BY ... LIMIT 来控制整体结果集——那样只会对合并后的结果排序限流,且必然触发临时表;真要取 Top-N,必须让每个分支自己裁剪数据。
- 错误写法:
(SELECT ... UNION ALL SELECT ...) ORDER BY score DESC LIMIT 20→ 先合并全部数据再排序,慢且内存爆 - 正确写法:
(SELECT ... ORDER BY score DESC LIMIT 20) UNION ALL (SELECT ... ORDER BY score DESC LIMIT 20) ORDER BY score DESC LIMIT 20 - 注意:外层
ORDER BY仍需存在,因为UNION ALL不保证顺序;但至少内层已大幅减少数据量 - 如果各子查询结果量级差异大(比如一个返回 10 行,一个返回 100 万行),可以按比例调小大表分支的
LIMIT,避免拖累整体
临时表引擎和排序缓冲区容易成瓶颈
即使用了 UNION ALL,如果结果集太大或字段太宽(比如含 TEXT、BLOB),MySQL 仍可能把中间结果落盘到磁盘临时表(On disk),这时 tmp_table_size 和 max_heap_table_size 就很关键。
- 查当前设置:
SHOW VARIABLES LIKE 'tmp_table_size';,默认通常才 16MB,远不够 - 临时表超限后降级为
MyISAM(5.7)或InnoDB(8.0+),IO 开销陡增 - 用
SHOW STATUS LIKE 'Created_tmp%';观察Created_tmp_disk_tables是否持续增长 - 字段能精简就精简:别
SELECT *,只取真正需要的列;VARCHAR(500)能改成VARCHAR(100)就改


















