UNION/UNION ALL 本身不使用索引,性能取决于各子查询是否有效利用索引;需为每个子查询的 WHERE、JOIN、ORDER BY 字段单独建索引,并避免外层 ORDER BY 导致索引失效。

集合查询(UNION/UNION ALL)本身不直接使用索引,但子查询是否走索引决定整体性能
MySQL 的 UNION 和 UNION ALL 是对多个 SELECT 结果集做合并操作,它本身不“走索引”,真正影响性能的是每个参与合并的子查询是否能有效利用索引。如果子查询写了 WHERE、ORDER BY 或 JOIN,而对应字段没建索引,那整个 UNION 就会慢得明显——因为每个子查询都在全表扫描。
- 子查询中用了
WHERE user_id = 123,但user_id没索引 → 全表扫两次,再合并,代价翻倍 -
UNION ALL比UNION快,因为它跳过去重逻辑;但去重本身不走索引,是内存 / 临时表排序完成的 - 如果子查询结果集很大,MySQL 可能用
Using temporary; Using filesort,这时即使子查询走了索引,合并阶段仍可能成为瓶颈
多表 UNION 场景下,索引要分别建在各子查询的过滤 / 连接字段上
比如你写:
SELECT id, name FROM users WHERE status = 'active' UNION ALL SELECT id, name FROM admins WHERE role = 'super'
,这两个表结构不同、数据独立,那就得各自优化:在 users(status) 和 admins(role) 上分别建索引。别指望一个索引跨表生效。
- 不要试图给
UNION后的结果加索引——结果集是虚拟的,不能建索引 - 若子查询含
JOIN,比如SELECT u.name FROM users u JOIN orders o ON u.id = o.user_id,则orders(user_id)必须有索引,否则JOIN退化为嵌套循环 - 字段类型要一致:如果
users.id是BIGINT,而admins.id是INT,UNION会 隐式转换,可能导致索引失效
ORDER BY + LIMIT 放在 UNION 外层时,索引大概率失效
这是高频踩坑点。例如:
(SELECT id, created_at FROM log_a WHERE type = 'error') UNION ALL (SELECT id, created_at FROM log_b WHERE type = 'error') ORDER BY created_at DESC LIMIT 10
,MySQL 会先把两个子查询结果全部取出、合并、排序、再截断——哪怕 created_at 有索引,也用不上外层排序。
- 解决方案:把
ORDER BY …… LIMIT下推到每个子查询里(前提是语义允许),比如:(SELECT id, created_at FROM log_a WHERE type = 'error' ORDER BY created_at DESC LIMIT 10) UNION ALL (SELECT id, created_at FROM log_b WHERE type = 'error' ORDER BY created_at DESC LIMIT 10) ORDER BY created_at DESC LIMIT 10 - 注意:下推后需在外层再
ORDER BY …… LIMIT,否则无法保证全局 Top 10 - 如果子查询本身无过滤条件(如纯
SELECT * FROM t),加索引也没用——全表扫完才排序
用 EXPLAIN 验证每个子查询是否真的用了索引
别只看最终 SQL 是否“跑出来”,要用 EXPLAIN 分别检查每个子查询。MySQL 8.0+ 支持 EXPLAIN FORMAT=TREE,能看清执行计划树;但更简单的是把 UNION 拆开,单独对每个 SELECT 执行 EXPLAIN。
- 关键看
type列:要是ALL或index,基本没走有效索引;ref、range、const才算靠谱 - 注意
key列是否显示实际使用的索引名;key_len能帮你判断是否用了联合索引的前缀 - 如果子查询用了
OR、LIKE '%abc'、函数包裹字段(如YEAR(created_at)),索引大概率已失效——这些规则在子查询里同样适用
索引不是贴在 UNION 上的膏药,而是长在每个子查询的 WHERE、JOIN、ORDER BY 字段上的肌肉。漏掉任何一个子查询的索引,整条 UNION 就可能拖着腿跑。






























