06 慢 SQL 怎么办: 执行计划、优化路径与经典误区

慢 SQL 调优最怕三件事: 凭感觉、看单点、乱加索引。真正高质量的优化,不是把数据库参数背一遍,而是沿着证据做判断。对 PostgreSQL 来说,最核心的证据就是执行计划、统计信息和实际资源消耗。

优化前先分清楚问题属于哪一类

所有慢 SQL 不外乎落在这几类里:

  • SQL 本身写法差
  • 索引设计不匹配查询路径
  • 统计信息失真,规划器选错路
  • 单条 SQL 不慢,但并发下互相争资源
  • 数据模型本身不适合当前访问模式

如果这一步不先分清,后面很容易越改越乱。

PostgreSQL 调优最有用的工作流

第一步: 把“感觉慢”变成“证据慢”

优先收集:

  • 完整 SQL
  • 执行频率
  • 平均耗时和 P95、P99
  • 是否只在高并发时变慢
  • 是否和某个时间段、某类参数有关

没有这些信息,调优很容易变成猜测。

第二步: 看真实执行计划

explain (analyze, buffers, verbose)
select ...

看计划时,先看下面几件事:

  • 走了什么扫描路径
  • 估算行数和实际行数差多少
  • 排序、哈希、聚合在哪一层
  • 哪个节点时间最长
  • 是否出现大量循环、回表、临时文件

第三步: 判断问题出在什么层

  • 如果估算和实际差很多,优先怀疑统计信息或数据分布。
  • 如果扫描行数巨大,优先怀疑索引或查询条件。
  • 如果排序、哈希特别重,优先怀疑返回集过大或内存不够。
  • 如果单条不慢、高并发变慢,优先怀疑锁、I/O、连接数或热点争抢。

四类常见误区

1. 只要慢就加索引

索引不是万能药。低选择性列、频繁更新列、不匹配查询顺序的列,乱建索引只会增加写入成本和维护负担。

2. 只看“有没有走索引”

走索引不一定快,不走索引也不一定错。关键要看:

  • 数据量多大
  • 返回行数多少
  • 过滤条件选择性如何
  • 是否需要排序

3. 忽视类型不一致

字段是 bigint,参数却按文本传;字段是时间戳,查询里却反复做函数转换。这类问题非常常见,而且经常直接影响执行计划。

4. 只优化 SQL,不优化访问模式

有些问题不是某条 SQL 写得差,而是业务本身在做深分页、大范围扫描、热点更新、N+1 查询。这个时候只改 SQL 作用有限,必须改访问策略。

特别值得掌握的几个优化点

复合索引顺序

索引列顺序应该围绕最常见过滤条件和排序需求来设计,不是把常用字段随便拼一起。

部分索引

如果业务大量查询只关心少数状态,比如“未完成订单”,部分索引往往比全量索引更划算。

覆盖索引

如果查询只需要少量字段,可以考虑 include,减少回表成本。

批量化和预计算

有些慢 SQL 的本质不是“算不出来”,而是“每次都现算太贵”。预聚合、物化结果、异步汇总,很多时候比硬调参数更有效。

一个实用判断

真正成熟的 PostgreSQL 调优,不会一上来就说“调大内存”“加几个索引”“升级机器”。它会先回答这几个问题:

  • 这条 SQL 到底慢在哪里
  • 规划器为什么会这么选
  • 这是单条问题、模式问题,还是架构问题
  • 最小成本的修复动作是什么

如果你能养成这种调优习惯,大部分慢 SQL 都不会再显得神秘。