MySQL中UNION子查询的ORDER BY必须配合LIMIT才生效,否则被优化器忽略;UNION先执行生成无序临时表,外层ORDER BY无法利用原索引,需确保各子查询ORDER BY字段、方向一致且外层严格匹配。

UNION执行顺序导致索引排序无法下推
MySQL的执行顺序是 SELECT → UNION → ORDER BY,这意味着每个子查询的 ORDER BY 在没有 LIMIT 的情况下会被优化器直接忽略。你写 (SELECT * FROM t1 ORDER BY id) UNION (SELECT * FROM t2 ORDER BY id),实际执行时两个 ORDER BY 都没生效——UNION 先把结果拼成一张无序临时表,外层再 ORDER BY 时面对的是没索引的内存/磁盘临时表,只能走 Using filesort。
字段类型或长度不一致强制构建临时表
UNION 要求所有分支对应列的类型和长度完全兼容。常见翻车点:
-
VARCHAR(50)和VARCHAR(100)合并 → MySQL 自动升格为VARCHAR(100),但这个转换发生在临时表阶段,原索引失效 -
INT与BIGINT混用 → 触发隐式转换,优化器放弃索引扫描,改走全表 + 临时表归并 -
utf8mb4_general_ci和utf8mb4_unicode_ci排序规则不同 → 即使都是VARCHAR(100),也会因 collation 不兼容而建临时表统一处理
用 EXPLAIN FORMAT=TRADITIONAL 查看每个分支,若出现 Using temporary,基本就是这类问题。
OR条件误用被优化器降级为UNION逻辑
当 WHERE 中含 OR 且两侧字段索引不完整(比如只有 a 有索引、b 没索引),优化器可能选择 index_merge,也可能干脆放弃索引走全表。更隐蔽的是:某些版本 MySQL 会把 A OR B 重写为等价的 (SELECT ... WHERE A) UNION DISTINCT (SELECT ... WHERE B) 形式,这时就继承了 UNION 的全部临时表开销。
验证方式:
MySQL 数据库管理技能,能够通过自然语言查询和管理数据库,支持 SELECT/INSERT/UPDATE/DELETE、表管理、备份恢复等操作。当用户提及 MySQL、数据库查询、建表或数据备份时使用此技能。
- 执行
EXPLAIN,看type是否为ALL或index_merge - 检查
Extra字段是否含Using temporary; Using filesort - 确认
b字段是否有索引;若没有,加索引后OR可能转为index_merge union(...),反而更快
没加LIMIT时子查询ORDER BY形同虚设
这是最容易被忽略的实操细节:UNION 子查询里的 ORDER BY 必须配合 LIMIT 才会被真正执行。不加 LIMIT,MySQL 认为“你只是想排个序,又不取前N条”,直接优化掉。
正确写法示例(假设 created_at 有索引):
(SELECT id, title, created_at FROM news WHERE status = 1 ORDER BY created_at DESC LIMIT 10000) UNION ALL (SELECT id, title, created_at FROM articles WHERE status = 1 ORDER BY created_at DESC LIMIT 10000) ORDER BY created_at DESC;
注意三点:
- 每个子查询都带
LIMIT(哪怕值很大,也得写) - 所有子查询
ORDER BY字段和方向必须严格一致 - 外层
ORDER BY必须与子查询完全相同,否则仍触发二次排序
临时表不是UNION本身的问题,而是你没给优化器留下可复用的有序输出路径。一旦子查询输出已按目标字段排好序,UNION ALL 就只是追加数据,根本不需要建临时表归并。

















