TATECHATLAS
◎ 简体中文
数据与数据库

新增索引前,先读懂 PostgreSQL 的 EXPLAIN

找到耗时环节,并区分规划器估算与实际测量。

本文内容

EXPLAIN 显示规划器预计的执行策略,EXPLAIN ANALYZE 则会真正运行查询并提供测量结果。决定新增索引前,应对比估计行数、实际行数和节点执行次数。

先查看估算计划

如果查询执行成本可能很高,先使用普通 EXPLAIN。计划中的 cost 不是毫秒。顺序扫描不一定有问题,读取小表的大部分数据时可能更合理。

EXPLAIN SELECT * FROM orders WHERE customer_id = 42;

在合适环境中测量

对可安全执行的 SELECT,检查行数、loops 和缓冲区访问。估算偏差很大时,应检查统计信息和数据分布。ANALYZE 也会执行写操作,不要随意用于修改数据的语句。

EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders WHERE customer_id = 42;

先读懂一行,再看整棵树

下面是示意计划片段,并非真实数据库测量。cost=0.43..8.61 表示规划器估计的启动成本与总成本,单位不是毫秒。rows=10 是预计输出行数,width=16 是预计每行平均字节数。

测量部分表示一次执行实际输出 10,000 行,而不是预计的 10 行。actual time=0.05..12.00 对应首次输出与完成的时间,单位为毫秒。如此明显的行数偏差,提示应先排查估计问题,而不是直接认定还缺一个索引。

Index Scan using orders_customer_idx on orders
  (cost=0.43..8.61 rows=10 width=16)
  (actual time=0.05..12.00 rows=10000 loops=1)

从叶子节点理解重复工作

缩进显示哪些节点向父节点提供数据。扫描结果可能进入连接、排序或聚合。找出工作量增长的位置:过滤丢掉大量行、排序处理很大集合,或内部节点反复执行。

普通节点的 actual rows 与时间是每次执行的平均值。例如 actual rows=5、loops=1000,所有执行大约共产生 5,000 行。节点时间可能包含子节点工作,直接相加会重复计数。并行计划还需要更多分析,可用总体 Execution Time 作为耗时参考。

结合缓冲区与统计信息判断

BUFFERS 提供单纯时间之外的数据访问信息。shared hit 表示在 PostgreSQL 共享缓冲区找到数据块;shared read 表示块被读入共享缓冲区,也可能来自操作系统缓存。这些是访问次数,不必然是不同数据块或真实磁盘读取次数。

过时统计或不均匀分布可能导致估计偏差。ANALYZE orders 收集表统计,而 EXPLAIN ANALYZE 实际执行查询,它们不是同一命令。大量数据变更之后,应先查看统计。排序溢出到磁盘,与连接行数估计错误,需要不同的诊断。

ANALYZE orders;

一次改变一个因素,公平比较

先提出假设:读取了太多无关记录,查找重复过多,或者中间结果排序过大。选择性较高的条件可能适合索引。小表或返回大部分记录的查询,顺序扫描仍可能合理。

比较时保持参数和数据可比,并重复测量:缓存与并发负载都会改变耗时。一次缓存已热的快速运行不能证明普遍改善。还要考虑索引空间和写入成本。先在合适环境分析安全的 SELECT;EXPLAIN ANALYZE 也会执行修改语句,因此可能产生副作用。

检查清单

  • 区分估计行数与实际行数。
  • 考虑重复次数和总工作量。
  • 更改索引前检查统计信息。

数据、缓存及环境会影响结果。只应在能承受相应负载的环境执行高成本查询。

参考来源

  1. PostgreSQL: using EXPLAIN ↗
  2. PostgreSQL: EXPLAIN command ↗
  3. PostgreSQL: performance tips ↗
  4. PostgreSQL: planner statistics ↗
返回顶部 ↑