1. 为什么面试官总爱问WHERE、HAVING和ON的区别?
每次面试数据库相关岗位,这三个关键词就像固定节目一样准时出现。我刚入行时也纳闷:它们不都是用来过滤数据的吗?直到有次在千万级数据表上写错条件导致查询超时,才真正理解执行顺序的差异对性能的影响有多大。
举个例子,去年我们电商系统做促销活动分析时,有个同事写了这样的SQL:
SELECT user_id, COUNT(order_id) FROM orders GROUP BY user_id HAVING create_time > '2023-01-01'结果查询跑了20分钟还没出结果。后来改成WHERE条件后,3秒就返回了数据。这就是典型的分不清过滤时机导致的性能问题。
2. WHERE与HAVING的本质区别
2.1 执行时机的关键差异
想象你是个快递站站长,WHERE就像在包裹入库时直接拒收不符合要求的快递(比如到付件),而HAVING是所有包裹按区域分好堆之后,再把不符合标准的整堆包裹扔掉。
具体来说:
- WHERE:在数据库引擎读取数据时立即过滤,相当于"原料质检"
- HAVING:在所有数据分组聚合完成后过滤,相当于"成品检验"
2.2 性能对比实测
我用MySQL的100万条订单数据做了组对照实验:
| 查询类型 | 执行时间 | 扫描行数 |
|---|---|---|
| WHERE条件 | 0.12s | 153,291 |
| HAVING条件 | 1.87s | 1,000,000 |
当使用WHERE status='paid'时,引擎先用索引过滤掉85%的未支付订单,再对剩余数据分组。而HAVING status='paid'会先扫描全表分组,最后才丢弃无效数据。
2.3 实际开发中的黄金法则
- 能用WHERE就别用HAVING:特别是当过滤条件不依赖聚合结果时
- HAVING专用场景:筛选聚合结果,如
HAVING AVG(score)>80 - 组合使用范例:
-- 查询2023年消费超过5次的VIP用户 SELECT user_id, COUNT(*) as order_count FROM orders WHERE user_level='VIP' AND create_time BETWEEN '2023-01-01' AND '2023-12-31' GROUP BY user_id HAVING COUNT(*) > 53. ON条件的特殊行为
3.1 内连接时的等价性
在inner join时,下面两种写法其实等效:
-- 写法1:条件全放在ON SELECT * FROM users u JOIN orders o ON u.id=o.user_id AND o.amount>1000 -- 写法2:连接条件放ON,过滤条件放WHERE SELECT * FROM users u JOIN orders o ON u.id=o.user_id WHERE o.amount>1000但建议采用写法2,因为:
- 语义更清晰(ON只管连接,WHERE负责过滤)
- 某些数据库优化器对WHERE条件优化更好
3.2 外连接时的"陷阱"
左连接时把右表过滤条件放错位置,结果可能天差地别:
-- 查询所有部门及其中薪资>1万的员工(错误写法) SELECT d.name, e.name FROM departments d LEFT JOIN employees e ON d.id=e.dept_id AND e.salary>10000 -- 正确写法 SELECT d.name, e.name FROM departments d LEFT JOIN employees e ON d.id=e.dept_id WHERE e.salary>10000 OR e.id IS NULL第一个查询会返回所有部门,但薪资条件可能不生效;第二个才是真正筛选高薪员工同时保留无员工部门。
3.3 执行计划解读
用EXPLAIN分析上述两个查询:
- 错误写法的过滤条件(
e.salary>10000)出现在JOIN的Extra列 - 正确写法的过滤条件出现在
WHERE子句
这说明:
- ON条件在连接时逐行判断
- WHERE条件在连接完成后整体过滤
4. 高级优化技巧
4.1 索引利用的差异
WHERE子句能充分利用索引,而HAVING通常无法使用索引。我曾优化过一个统计查询,通过把HAVING create_date>'2023-01-01'改为WHERE条件,同时给create_date字段加索引,查询时间从8秒降到0.2秒。
4.2 子查询优化策略
对于复杂聚合查询,可以分阶段处理:
-- 原始低效写法 SELECT department, AVG(salary) FROM employees GROUP BY department HAVING AVG(salary) > (SELECT AVG(salary) FROM employees) -- 优化后写法 WITH dept_avg AS ( SELECT department, AVG(salary) as avg_sal FROM employees GROUP BY department ) SELECT * FROM dept_avg WHERE avg_sal > (SELECT AVG(salary) FROM employees)4.3 分区表特别注意事项
当使用分区表时,WHERE条件要包含分区键才能触发分区裁剪。有次我们查询按月分区的日志表,忘记在WHERE加月份条件,导致全表扫描。
5. 常见面试问题破解
面试官可能会追问:
"为什么这个HAVING条件要改成WHERE?"
- 从执行顺序、中间结果集大小、索引利用三个维度回答
"LEFT JOIN时ON和WHERE放错会怎样?"
- 用具体例子说明结果差异,最好能画数据流图示
"如何优化这个包含HAVING的慢查询?"
- 建议改为WHERE条件
- 考虑使用临时表分步处理
- 检查相关字段索引
有次面试我遇到个刁钻问题:"HAVING里能不能用窗口函数?" 正确答案是:
-- 合法的HAVING使用窗口函数 SELECT department, AVG(salary) as avg_sal FROM employees GROUP BY department HAVING AVG(salary) > ( SELECT AVG(avg_sal) OVER() FROM (SELECT AVG(salary) as avg_sal FROM employees GROUP BY department) t )但实际开发中应该避免这种写法,可读性和性能都很差。