UNION/UNION ALL本身不使用索引,性能取决于各子查询是否有效利用索引;需为每个子查询的WHERE、JOIN、ORDER BY字段单独建索引,并避免外层ORDER BY导致索引失效。
MySQL 的 UNION 和 UNION ALL 是对多个 SELECT 结果集做合并操作,它本身不“走索引”,真正影响性能的是每个参与合并的子查询是否能有效利用索引。如果子查询写了 WHERE、ORDER BY 或 JOIN,而对应字段没建索引,那整个 UNION 就会慢得明显——因为每个子查询都在全表扫描。
WHERE user_id = 123,但 user_id 没索引 → 全表扫两次,再合并,代价翻倍UNION ALL 比 UNION 快,因为它跳过去重逻辑;但去重本身不走索引,是内存/临时表排序完成的Using temporary; Using filesort,这时即使子查询走了索引,合并阶段仍可能成为瓶颈比如你写:
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 会隐式转换,可能导致索引失效这是高频踩坑点。例如:
(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 10SELECT * FROM t),加索引也没用——全表扫完才排序别只看最终 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 就可能拖着腿跑。