新增索引前,先读懂 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 也会执行修改语句,因此可能产生副作用。
检查清单
- 区分估计行数与实际行数。
- 考虑重复次数和总工作量。
- 更改索引前检查统计信息。
适用范围
数据、缓存及环境会影响结果。只应在能承受相应负载的环境执行高成本查询。