索引失效、join顺序、having误用、子查询重写是四大常见sql性能陷阱;应避免where中对字段用函数、确保小表驱动、将过滤条件尽量前移至where、慎用子查询自动优化。

WHERE 条件里用函数导致索引失效
数据库执行 WHERE UPPER(name) = 'JOHN' 这类写法时,几乎必然走全表扫描。因为索引是按原始值构建的,而函数改变了字段的可比较形式,优化器无法利用索引定位。
- 改成
WHERE name = 'john'并确保字段存储格式统一(比如入库就转小写) - 实在要大小写不敏感,用
COLLATE utf8mb4_0900_as_cs(MySQL 8.0+)或建函数索引:CREATE INDEX idx_name_upper ON users ((UPPER(name))) - PostgreSQL 可直接建
CREATE INDEX idx_name_lower ON users (LOWER(name)),但注意查询必须写成WHERE LOWER(name) = 'john'才命中
JOIN 顺序错乱引发临时表和文件排序
MySQL 5.7 默认用嵌套循环(NLJ),如果 JOIN 时把大表放前面、小表放后面,驱动表过大,就会反复扫描被驱动表,还可能触发 Using temporary; Using filesort。
- 用
EXPLAIN看rows和type:如果出现ALL或index且rows数万以上,大概率有问题 - 让小结果集当驱动表——不是看原表大小,而是看
WHERE过滤后的行数;必要时加STRAIGHT_JOIN强制顺序(慎用) - 多表
JOIN时,避免在ON条件里混用非关联字段,比如ON a.id = b.a_id AND b.status = 'active',这会让优化器难估算
GROUP BY 后的 HAVING 误当 WHERE 用
HAVING 是对分组后结果过滤,WHERE 是对原始行过滤。把能写进 WHERE 的条件硬塞进 HAVING,等于先分组再丢弃,白白浪费 CPU 和内存。
- 例如统计每个部门平均薪资 > 15000 的人:错误写法是
GROUP BY dept HAVING AVG(salary) > 15000;正确做法是先筛掉salary 的记录再分组 -
HAVING只该用于含聚合函数的条件,比如HAVING COUNT(*) > 5;普通字段比较一律放WHERE - 某些 ORM 自动生成的 SQL 容易犯这个错,记得检查执行计划里的
Extra字段是否出现Using where; Using temporary
子查询被重写为 JOIN 后性能反而更差
MySQL 5.6+ 会自动把部分 IN 子查询转成 SEMI-JOIN,听起来挺好,但若子查询结果集极小(比如几十行),而外表极大,这种转换反而导致外表全扫 + 多次哈希匹配,比原来走索引快得多。
- 用
SELECT /*+ NO_SEMIJOIN() */ ...(MySQL)或/*+ NO_UNNEST */(Oracle)强制禁用重写 - 确认子查询是否走索引:
EXPLAIN看子查询部分的type是否为const或ref;如果是ALL,说明子查询本身就有问题 - 用
EXISTS替代IN更可控,尤其当子查询涉及NULL值时,IN语义容易出错
真正卡住性能的往往不是语法多复杂,而是某一处索引没建对、某个 WHERE 条件悄悄绕过了索引、或者执行计划里一个没注意的 Using temporary —— 这些地方不看 EXPLAIN,光靠猜,十有八九调错方向。










