“写得好的 SQL,懂得把条件说在前面。”
在日常 Java 开发中,我们常写各种复杂的 SQL:嵌套查询、视图、分页、聚合……而当查询语句一旦嵌套了子查询或视图,性能就可能扑街。
于是,一个非常重要的优化技术就显得格外关键:条件下推(Predicate Pushdown) 。
今天我们不说虚的,从实际例子出发,一口气把这招讲透!
🧠 条件下推到底是啥?
通俗地说:
条件下推是一种优化器层面的 SQL 优化策略,
把原本在外层执行的 WHERE 条件,尽可能提前“下推”到子查询或视图中执行,
这样能更早过滤无用数据,减少中间结果量,提高执行效率。
🌰 来个例子看不下推 vs 下推的区别
表结构
sql
复制编辑
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(50),
department_id INT,
salary INT
);
我们写了个看似合理的 SQL 👇:
sql
复制编辑
SELECT * FROM (
SELECT * FROM employees WHERE department_id = 10
) AS e
WHERE salary > 10000;
🔍 没有条件下推的情况下
执行顺序:
- 先执行子查询:
SELECT * FROM employees WHERE department_id = 10
- 得到一个临时表
e
- 外层再从 e 中筛选:
salary > 10000
⚠️ 问题:如果 department_id = 10
的人很多,哪怕 salary > 10000
的人很少,也会先把大堆人“查出来”,再过滤。
✅ 启用条件下推的情况下
MySQL 优化器会聪明地把 salary > 10000
也放到子查询中一起执行!
变成 👇:
sql
复制编辑
SELECT * FROM employees WHERE department_id = 10 AND salary > 10000;
这才是我们想要的效果:查询更早过滤数据,避免生成大中间表,临时表都不用建了!
🔎 实战分析:用 EXPLAIN 看下推效果
sql
复制编辑
EXPLAIN SELECT * FROM (
SELECT * FROM employees WHERE department_id = 10
) AS e
WHERE salary > 10000;
观察:
- 是否只看到子查询一步(说明优化器已下推)
filtered
百分比是否变小- 如果启用了临时表/Using temporary,说明没下推成功
更直观方式是:和这条语句的 EXPLAIN 结果对比:
sql
复制编辑
EXPLAIN SELECT * FROM employees WHERE department_id = 10 AND salary > 10000;
如果两者执行计划几乎一致,说明优化器下推成功了 ✅
📌 条件下推最常见的应用场景
场景类型 | 是否支持条件下推 | 说明 |
---|---|---|
子查询 | ✅ 是 | 经典用途 |
视图 | ✅ 是 | View 是语法糖,本质是子查询 |
分区表 | ✅ 是 | 与分区裁剪结合效果更佳 |
JOIN | ⚠️ 有限支持 | 复杂 JOIN 条件不一定能下推 |
外部数据源 | ✅ 是 | 如 Hive、ClickHouse 外部表 |
🚨 注意:不是所有情况都能下推!
这些情况容易让优化器“放弃下推”:
情况 | 影响 |
---|---|
外层用了函数包装字段 | 无法识别可下推条件 |
外层字段是表达式或别名 | 不能反向推导条件 |
使用了 GROUP BY 或 HAVING | 聚合后无法再细化过滤条件 |
子查询用了 LIMIT 、DISTINCT | 限制了优化器自由度 |
例子:
sql
复制编辑
SELECT * FROM (
SELECT salary * 1.1 AS new_salary FROM employees
) AS e
WHERE new_salary > 10000;
👉 这种就无法下推,因为 new_salary > 10000
没法“还原”到底层字段。
🧰 实战建议 for Java 技术栈开发者
- 避免在子查询中 return 表达式字段(别名 + 运算)
- 多用
EXPLAIN
验证优化效果,确认是否用了Using where
- 大型系统中使用视图时,建议WHERE 条件不要都放在外层
- 合理使用 MyBatis XML + 动态 SQL,让条件直接拼入子查询中
✨ 总结口诀
👇
“能早筛选,就别晚过滤;条件不下推,优化全白费。”
🔥 彩蛋扩展:条件下推 VS 索引下推
项目 | 条件下推(Predicate Pushdown) | 索引下推(Index Condition Pushdown) |
---|---|---|
执行阶段 | 优化器逻辑阶段 | 存储引擎执行阶段 |
目标 | 提前过滤,减少中间表 | 减少回表次数 |
作用位置 | 子查询 / 视图 / 分区等 | InnoDB 索引扫描过程中 |
是否依赖索引 | 否 | 是 |
👉 它们是两种不同层级的优化技术,可以协同作战。
🧙♂️ 最后一句话:
想写出高性能 SQL,别只盯着索引,学会引导优化器聪明地下推条件,才是真正的大师手笔。