SQL 性能优化的正确顺序

执行计划是优化SQL的第一步,必须先用EXPLAIN等命令分析;若出现全表扫描或统计信息偏差大,应先ANALYZE而非盲目加索引;复合索引字段顺序、函数滥用、低效分页等均会导致索引失效。

先看执行计划,再动索引

没有执行计划就加索引,大概率白忙活。MySQL 的 EXPLAIN、PostgreSQL 的 EXPLAIN ANALYZE、SQL Server 的 SET STATISTICS XML ON,都是必须第一步跑的命令。重点看 type(MySQL)或 Node Type(PG),如果出现 ALLSeq Scan,说明走了全表扫描;再看 rows 预估和实际行数是否偏差极大——这往往意味着统计信息过期,ANALYZE 比加索引更紧迫。

  • 别只盯着“有没有用上索引”,更要关注“用了哪个索引”和“用了几层索引条件”
  • 复合索引字段顺序不对,WHERE a = ? AND b > ?(b, a) 就基本失效
  • 某些 ORM 自动生成的 ORDER BY RAND()LIMIT 10000 OFFSET 10000,加索引也救不了,得换逻辑

WHERE 条件里的函数调用是隐形杀手

WHERE YEAR(created_at) = 2025WHERE UPPER(name) = 'JOHN' 会让索引完全失效。数据库无法用 B+ 树直接匹配函数结果,只能回表或全扫。

  • 改写为范围查询:created_at >= '2025-01-01' AND created_at
  • 需要大小写不敏感查询,建函数索引(PG/Oracle 支持 CREATE INDEX ON users ((UPPER(name)));MySQL 8.0+ 支持函数索引,但需注意表达式必须确定性
  • LIKE '%abc' 无法走索引,LIKE 'abc%' 可以——如果业务真要前缀模糊,考虑倒排或全文索引

JOIN 顺序和驱动表选错,索引也白搭

优化器不总是对的。小表驱动大表仍是硬道理,尤其在 MySQL 中,STRAIGHT_JOIN 有时比依赖优化器更稳。看 EXPLAINtable 列顺序和 rows 乘积,如果驱动表预估返回 10 万行,被驱动表即使有索引,也要做 10 万次索引查找+回表,I/O 压力爆炸。

  • 确保 JOIN 字段类型严格一致:一个是 INT,一个是 BIGINT,或一个是 VARCHAR(50)、一个是 VARCHAR(100),都可能导致隐式转换,跳过索引
  • 多表 JOIN 时,优先把带高选择性过滤条件(如 status = 'paid')的表放在前面作为驱动表
  • 临时表(SELECT ... FROM (SELECT ...) t)默认无索引,必要时用 CTE + MATERIALIZED(PG 12+)或物化中间结果

避免 SELECT * 和 N+1 查询

查 100 行却返回 50 列,其中 45 列根本不用,不仅网络和内存浪费,还可能让覆盖索引失效(因为要回表取没索引的字段)。更隐蔽的是 ORM 中

的懒加载:一个 users 查询后,循环里调 user.posts,触发 N 次 SQL。

  • 显式列出所需字段,尤其是宽表场景下,能显著减少 Buffer Pool 压力
  • JOIN 一次性拉取关联数据,或用批量 ID 查询(WHERE post.user_id IN (1,2,3...))替代循环单查
  • PostgreSQL 中 SELECT COUNT(*) 在大表上极慢?确认是否真需要精确值——很多场景用估算值(pg_class.reltuples)就够了

真实瓶颈常藏在「看起来最不该出问题」的地方:比如一个加了索引的字段,因字符集不同导致连接失效;或者一个 ORDER BY 字段没进索引,却让整个执行计划退化成 filesort。优化不是线性流程,而是反复对照执行计划、验证假设、缩小范围的过程。