解剖执行计划
目标
从头到尾阅读 EXPLAIN 输出,熟悉应按什么顺序查看哪些数字。
为什么重要
执行计划中的节点名称本身并不能说明好坏。Seq Scan 明显可能比索引扫描更快,Nested Loop 也常常是最佳选择。优化器在已有统计信息范围内几乎总会作出合理判断。
因此,计划看似异常时,不应问“优化器为什么这么笨”,而应问:**“我向优化器提供了什么错误信息?”**估算行数与实际行数的差距会给出答案。
阅读顺序如下:先看 Execution Time 与 Planning Time;然后比较每个节点的估算 rows 与实际 rows,找到差距达到一个数量级以上的最内层节点;loops 不为 1 时通过乘法计算真实贡献;在 Rows Removed by Filter 较大的节点寻找索引机会;最后根据 Buffers 的 read 比例区分计划问题与缓存问题。
步骤
所有输出文件保存到 /root/exp/。
- 为任意查询
orders的语句只添加 EXPLAIN,执行后把输出保存到/root/exp/plan_basic.txt。不得包含实测值(actual time)。 - 为同一查询添加真实执行选项,保存到
/root/exp/plan_analyze.txt。必须包含actual time与Execution Time。 - 把包含块读取统计的 join 查询计划保存到
/root/exp/plan_buffers.txt,必须有Buffers: shared ...行。 - 创建表
skewed_events,列为id、kind,至少 10 万行,其中kind为rare的行不超过总数 1%。创建后必须收集统计信息,让 planner 知道行数。 - 暂时关闭其他 join 方法后执行 join 查询,把计划保存到
/root/exp/plan_loops.txt。必须包含Nested Loop,且loops=至少为三位数。 - 实测执行会选择 hash join 的查询,保存到
/root/exp/plan_hash.txt。必须包含Hash Join与Batches:。 - 分别在默认状态和禁用 sequential scan 的状态下实测执行同一查询,把两个结果依次追加到
/root/exp/plan_forced.txt。必须包含Seq Scan、使用索引的节点,并至少两次出现Execution Time。 - 实测执行条件由
Index Cond处理、且没有Rows Removed by Filter的计划,保存到/root/exp/plan_fixed.txt。
参考
- 实用组合:
EXPLAIN (ANALYZE, BUFFERS) SELECT ... - 关闭 join 方法:
SET enable_hashjoin = off; SET enable_mergejoin = off; - 关闭 sequential scan:
SET enable_seqscan = off;——**仅供诊断。**不要作为生产设置保留。 - 常见错误 1:第 1 步使用 ANALYZE 会产生实测值,导致评分失败。
- 常见错误 2:第 4 步只建表却遗漏统计信息收集,planner 就不知道行数。
不执行,只查看计划
为任意查询 orders 的语句只添加 EXPLAIN,执行后把输出保存到 /root/exp/plan_basic.txt。不得包含实测值(actual time)。
不加选项,只添加一个关键字,就不会执行查询,只显示估算值。
查看带实测值的计划
为同一查询添加真实执行选项,保存到 /root/exp/plan_analyze.txt。必须包含 actual time 与 Execution Time。
有一个选项会真实执行并测量。用于写查询时,请放在事务中。
启用块读取统计
把包含块读取统计的 join 查询计划保存到 /root/exp/plan_buffers.txt,必须有 Buffers: shared ... 行。
有一个选项可以区分缓存命中与磁盘读取。括号中可以用逗号列出多个选项。
创建偏斜数据表并收集统计
创建表 skewed_events,列为 id、kind,至少 10 万行,其中 kind 为 rare 的行不超过总数 1%。创建后必须收集统计信息,让 planner 知道行数。
用 generate_series 创建至少 10 万行,并让特定值以不超过 1% 的比例稀少出现。创建后必须收集统计信息,planner 才会知道。
观察高循环次数的 Nested Loop
暂时关闭其他 join 方法后执行 join 查询,把计划保存到 /root/exp/plan_loops.txt。必须包含 Nested Loop,且 loops= 至少为三位数。
暂时禁用其他 join 方法后会选择 Nested Loop。请检查计划中的 loops 值。
检查 Hash Join 的批次数
实测执行会选择 hash join 的查询,保存到 /root/exp/plan_hash.txt。必须包含 Hash Join 与 Batches:。
Hash 节点的实测信息只有真实执行后才会出现。请确认 Batches 是 1 还是更大。
实测比较强制前后
分别在默认状态和禁用 sequential scan 的状态下实测执行同一查询,把两个结果依次追加到 /root/exp/plan_forced.txt。必须包含 Seq Scan、使用索引的节点,并至少两次出现 Execution Time。
分别在默认状态和禁用 sequential scan 后运行同一查询,并把结果追加到同一个文件。
把 Filter 移到 Index Cond
实测执行条件由 Index Cond 处理、且没有 Rows Removed by Filter 的计划,保存到 /root/exp/plan_fixed.txt。
目标是从一开始就不读取,而不是读取后丢弃。计划中的 Rows Removed by Filter 行必须消失。