SQL 复杂查询技巧与优化

where子句中对字段使用函数(如upper(name)、date(created_at))会导致索引失效,应改为将函数移至右侧或用范围条件替代,以避免全表扫描。

SQL 复杂查询技巧与优化

WHERE 子句里用函数导致索引失效

只要 WHERE 条件对字段套了函数,比如 WHERE UPPER(name) = 'JOHN'WHERE DATE(created_at) = '2024-01-01',绝大多数数据库(MySQL、PostgreSQL、SQL Server)都会跳过索引,走全表扫描。

真正该做的不是“怎么让函数走索引”,而是绕开它:

  • 把函数移到右边:用 WHERE name = UPPER('john') 替代 UPPER(name) = 'JOHN'
  • 用范围替代函数:用 WHERE created_at >= '2024-01-01' AND created_at 替代 <code>DATE(created_at) = '2024-01-01'
  • 建函数索引(仅 PostgreSQL / MySQL 8.0+ 支持):CREATE INDEX idx_upper_name ON users (UPPER(name)),但得确认业务真需要且查询频率高

JOIN 多表时驱动表选错,性能断崖式下跌

数据库执行 JOIN 不是“随便挑一个表开始”,而是先选驱动表(outer table),再用它的每行去查被驱动表(inner table)。如果驱动表是 100 万行的大表,而被驱动表只有 100 行,但没走索引——那就等于做 100 万次随机 I/O。

判断和干预的关键点:

  • 看执行计划里的 EXPLAIN 输出,重点关注 type 字段:要是出现 ALLindex,说明没走好索引
  • 小表优先做驱动表:显式用 STRAIGHT_JOIN(MySQL)或在 FROM 中把预期的小表放前面
  • 确保被驱动表的关联字段有索引:比如 LEFT JOIN orders o ON u.id = o.user_id,必须在 orders.user_id 上建索引

GROUP BY + 聚合后还要排序,ORDER BY 别乱加

GROUP BY 本身不保证顺序,但很多人会顺手加个 ORDER BY id,结果发现慢得离谱——尤其当分组后结果集大、又没索引支撑排序字段时。

更实际的做法:

  • 如果只是想按分组字段排序,直接写 GROUP BY x ORDER BY x,多数引擎能复用分组的临时排序结构
  • 如果要按聚合结果排序,比如 ORDER BY COUNT(*) DESC,就别指望索引了,但可以加 LIMIT 控制输出量,避免排序整个中间结果集
  • 避免在 SELECT * 后用 GROUP BY:MySQL 5.7+ 严格模式下会报错,老版本可能返回不可靠值;明确列出所有非聚合字段

子查询写成 WHERE IN (SELECT …) 就容易卡死

这个写法看着直观,但 MySQL 在 5.6 及更早版本里,IN 子查询默认是“循环执行”:外层每一条,都跑一遍子查询。哪怕子查询只返回 10 行,外层 10 万行就是 100 万次执行。

换种写法,效果立竿见影:

  • 改用 JOIN:把子查询提前物化成临时表,再关联,执行一次即可
  • EXISTS 替代(尤其子查询带条件时):WHERE EXISTS (SELECT 1 FROM logs l WHERE l.user_id = u.id AND l.status = 'error')
  • 确认子查询是否真的需要实时计算:能预聚合的,就用 CREATE TEMPORARY TABLE 先存好结果

复杂点在于,不同数据库对子查询的优化策略差异很大——PostgreSQL 通常自动转成哈希连接,MySQL 则高度依赖写法和版本。别信“语法一样性能就一样”。