集合查询(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 就可能拖着腿跑。
