UNION(尤其UNION ALL)天然阻碍谓词下推,因各分支列名、NULL处理、类型转换不一致,易致漏行错行;主流数据库默认禁用下推,GROUP BY+UNION更成“死亡区”,物化后索引与ICP全失效,需改用CTE或显式条件重写。

UNION(尤其是UNION ALL)天然破坏谓词下推的语义可拆分性
UNION 把多个独立结果集拼成一个逻辑结果,但优化器无法保证外层条件能安全、等价地分发到每个分支——因为各分支可能有不同列名、不同NULL处理逻辑、甚至不同数据类型隐式转换行为。一旦下推,就可能漏行或错行。MySQL 8.0+、TiDB、PolarDB-X 等主流系统在遇到含 UNION 的子查询时,默认放弃下推,连 WHERE 中纯单表条件(如 t.name LIKE '%x%')都常被卡在物化后过滤。
GROUP BY + UNION 组合是下推“死亡区”
当 UNION 的每个分支还带 GROUP BY(比如两个聚合视图合并),问题更严重:外层条件若想下推,必须同时满足两个约束——既要能拆到每个分支,又要不改变分组语义。而优化器看到 GROUP BY 就会保守禁用下推,哪怕你只查一个 AGENT_ID = 123。实测中,EXPLAIN ANALYZE 显示 rows_read 和单独执行子查询几乎一致,说明条件根本没进扫描层。
UNION 子查询被物化后,索引和ICP全部失效
MySQL 对含 UNION 的派生表默认走 TEMPTABLE 算法,生成临时内存/磁盘表;PostgreSQL 则倾向生成 SubqueryScan 节点。无论哪种,原始基表的索引结构都不可见,导致:
-
WHERE age > 30只能在 Server 层过滤,无法触发 InnoDB 的索引下推(ICP) - 即使你在
QUERY_DES(AGENT_ID, PREM)上建了联合索引,UNION 视图里也用不上 - 外层
JOIN条件无法参数化注入,每个分支仍全表扫
替代方案比硬调参数更可靠
别指望靠 SET optimizer_switch='derived_condition_pushdown=on' 或提示(hint)强行打开——UNION 场景下这些开关多数被忽略。更实际的做法是:
- 把 UNION 拆成独立 CTE(MySQL 8.0+/PostgreSQL 支持),并确保每个 CTE 不含聚合,部分条件下推可能恢复
- 用
UNION ALL+WHERE分支重写:把外层过滤条件显式复制进每个SELECT分支(如SELECT ... FROM t1 WHERE agent_id = 123 UNION ALL SELECT ... FROM t2 WHERE agent_id = 123) - 如果只是为去重,优先用
DISTINCT替代UNION,后者下推支持度略高
真正麻烦的是那些已经上线、嵌在视图里的 UNION + GROUP BY ——它不报错,但每次执行都在默默扫全表。验证时别只看 EXPLAIN 有没有 “pushdown” 字样,盯紧 rows_read 和 cop_task 的实际过滤范围才是关键。

















