mysql没有“union索引”,union优化or的本质是让各分支独立使用单列或覆盖索引;explain中显示的是各子查询的独立索引使用情况,而非整体索引;改写前须确认每分支能走索引、字段有对应单列索引、无隐式转换。

不能靠“命中UNION索引”来优化OR查询——MySQL根本没有叫“UNION索引”的东西。所谓“用UNION优化OR”,本质是绕过OR本身对索引的破坏性,让每个分支独立走各自字段上的有效索引。真正起作用的是单列索引或覆盖索引,不是UNION带来的某种新索引类型。
为什么EXPLAIN里看不到UNION相关的索引提示
UNION(或UNION ALL)只是SQL语法结构,不改变底层索引使用逻辑。MySQL执行时会分别执行每个SELECT子句,各自走自己的索引路径,然后合并结果集。所以你在EXPLAIN中看到的key、type、rows等指标,都是针对每个子查询单独输出的——不是整体一个“UNION索引”,而是多个独立的ref/const扫描。
常见错误现象:
- 改写后EXPLAIN仍显示某一分支的type为ALL,说明该分支没走索引
- 两个子查询都显示key为NULL,大概率是字段没建索引,或存在隐式类型转换
- 用了UNION但没加括号,导致语法解析错误:
ERROR 1222 (21000): The used SELECT statements have a different number of columns
UNION ALL改写前必须验证的三件事
不是所有OR都能安全拆。动手前先确认:
- 每个OR分支单独执行时,
EXPLAIN SELECT * FROM t WHERE branch_1的type必须是ref、const或range,且key非NULL - 涉及的字段必须有对应单列索引:比如
status = 1 OR city = 'Beijing',就得有INDEX(status)和INDEX(city);联合索引(status, city)对后者无效 - 排除隐式转换:如
phone VARCHAR(20)字段写成WHERE phone = 13800138000,该分支必然全表扫描,拖垮整体性能
ORDER BY和LIMIT必须下推到每个子查询
很多人只在外层加ORDER BY created_at DESC LIMIT 20,结果查出来的不是Top 20,而是每个分支各取20再合并——数据错乱、分页偏移。
正确做法是估算上界后分别限制,例如:
(SELECT id, name, created_at FROM orders WHERE status = 1 ORDER BY created_at DESC LIMIT 30) UNION ALL (SELECT id, name, created_at FROM orders WHERE type = 'urgent' ORDER BY created_at DESC LIMIT 30) ORDER BY created_at DESC LIMIT 20
注意:
- 子查询里的
LIMIT不是精确值,而是按业务重叠度预估(如两条件交集极少,可设为20;若可能大量重合,建议30~50) - 外层
ORDER BY不可省——UNION ALL不保证顺序,不加就会返回无序结果 - 如果原查询有
GROUP BY或聚合函数,不能直接拆,语义已不等价
IN比UNION ALL更简单的情况别硬拆
当OR只作用于同一字段的多个等值判断时,IN是更优解,优化器处理成熟、无需额外索引、语义清晰:
- ✅
status = 'paid' OR status = 'shipped' OR status = 'delivered'→ 直接改status IN ('paid', 'shipped', 'delivered') - ❌
status = 'paid' OR city = 'Shanghai'→ 必须拆UNION ALL,且确保city有单列索引 - ⚠️
id > 100 OR created_at > '2024-01-01'→ 拆了也会重复,且范围条件选择度低,不如检查是否能加覆盖索引或改用分区表
最容易被忽略的一点:UNION ALL改写后,如果某个分支因统计信息过期或索引碎片严重而没走索引,整个优化就白做了。上线前务必在生产镜像环境跑一次EXPLAIN,逐条核对每个子查询的执行计划。











