真正拖慢 union all 视图的不是 union all 操作符,而是每个子查询未独立优化:未下推条件、未走索引、类型不一致致隐式转换、外层 order by/limit 强制物化全部结果。

UNION ALL 视图本身不慢,慢的是你没意识到它每次都在重执行全部子查询,且中间结果无法被索引加速——性能落差几乎全来自子查询未独立优化,而不是 UNION ALL 这个操作符。
每个子查询必须单独跑 EXPLAIN 看执行计划
视图只是 SQL 定义,数据库不会跨 UNION ALL 子句做全局优化。你得把每个 SELECT 当成独立查询来调优:
- 对视图里每个
SELECT单独执行EXPLAIN SELECT ...,确认type是ref或range,而不是ALL(全表扫描) - 检查
key列是否命中索引;如果显示NULL,说明 WHERE 条件没走索引 - 特别注意
rows估算值:若某子查询预估扫描 50 万行,而实际只返回 10 行,大概率是缺少覆盖索引或写了SELECT *
外层加 WHERE 不会自动下推到子查询
这是最常被忽略的坑:你在 SELECT * FROM my_union_view WHERE status = 'active' 里写的条件,几乎不会下推进每个 SELECT 内部。数据库先执行全部子查询、拼出完整中间结果,再过滤——哪怕你只想要 10 行。
- 正确做法是把过滤条件显式写进每个子查询:
(SELECT id FROM t1 WHERE status = 'active') UNION ALL (SELECT id FROM t2 WHERE status = 'active') - 如果子查询来自分片表(如
orders_2023,orders_2024),且业务上天然无交集,就更不该依赖外层过滤 - MySQL 8.0 对视图内联优化仍较弱,嵌套两层以上时,WHERE 下推基本失效
类型不一致触发隐式转换,索引直接失效
两个子查询对应列类型稍有差异(比如 VARCHAR(50) vs VARCHAR(255),或 INT vs CHAR),MySQL 会悄悄做隐式转换——这会导致该列上的索引完全无法使用。
- 用
SHOW CREATE VIEW my_union_view检查各子查询对应列的实际类型 - 统一显式转类型:
CAST(col AS CHAR(50))或CONVERT(col, CHAR(50)),避免依赖自动推导 - NOT NULL 和 NULL 属性不一致也会报错(尤其在严格模式下),不是“变慢”,而是根本执行失败
ORDER BY 和 LIMIT 放在视图外会强制物化全部结果
哪怕你只想要前 10 行,只要外层有 ORDER BY 或 LIMIT,数据库就必须先把所有 UNION ALL 子查询的结果全拉出来,再排序取头——中间结果一旦超过 tmp_table_size,立刻写磁盘。
- 避免在视图定义里写
ORDER BY(MySQL 会直接报错;PostgreSQL 允许但无加速效果) - 改用“子查询内部分页”:每个子查询加
LIMIT 1000,再在外层统一ORDER BY ... LIMIT 10 - 高频稳定查询可考虑物化:定时把
UNION ALL结果写入一张summary_union表,并建好索引
真正拖慢 UNION ALL 视图的,从来不是 UNION ALL 本身,而是每个子查询都像裸写一样没加约束、没建索引、没控制字段——它暴露的是基表查询的原始缺陷,只是被视图封装后更难定位了。










