SQL 复杂查询与优化策略

1次阅读

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

SQL 复杂查询与优化策略

WHERE 条件里用函数导致索引失效

数据库执行 WHERE UPPER(name) = 'JOHN' 这类写法时,几乎必然走全表扫描。因为索引是按原始值构建的,而函数改变了字段的可比较形式,优化器无法利用索引定位。

  • 改成 WHERE name = 'john' 并确保字段存储格式统一(比如入库就转小写)
  • 实在要大小写不敏感,用 COLLATE utf8mb4_0900_as_csmysql 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

  • EXPLAINrowstype:如果出现 ALLindexrows 数万以上,大概率有问题
  • 让小结果集当驱动表——不是看原表大小,而是看 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 是否为 constref;如果是 ALL,说明子查询本身就有问题
  • EXISTS 替代 IN 更可控,尤其当子查询涉及 NULL 值时,IN 语义容易出错

真正卡住性能的往往不是语法多复杂,而是某一处索引没建对、某个 WHERE 条件悄悄绕过了索引、或者执行计划里一个没注意的 Using temporary —— 这些地方不看 EXPLAIN,光靠猜,十有八九调错方向。

text=ZqhQzanResources