理解PostgreSQL查询执行计划(一)

初识EXPLAIN的困惑

每个新晋DBA接触PostgreSQL时学到的第一个命令就是"EXPLAIN"。但第一次尝试理解它的输出时,往往会遇到令人困惑的情况:

Sort  (cost=238.32..240.39 rows=826 width=961)
   Sort Key: n.nspname, p.proname, (pg_get_function_arguments(p.oid))
   ->  Hash Join  (cost=1.25..198.30 rows=826 width=961)
         Hash Cond: (p.pronamespace = n.oid)
         ->  Seq Scan on pg_proc p  (cost=0.00..182.94 rows=1009 width=692)
               Filter: pg_function_is_visible(oid)
         ->  Hash  (cost=1.14..1.14 rows=9 width=117)
               ->  Seq Scan on pg_namespace n  (cost=0.00..1.14 rows=9 width=117)
                     Filter: (nspname <> ALL ('{pg_catalog,information_schema}'::name[]))
(9 rows)

这个复杂的执行计划输出对初学者来说简直像天书一般。让我们从基础开始,逐步揭开EXPLAIN的神秘面纱。

PostgreSQL的智能之处

首先需要理解一个关键概念:PostgreSQL"知道"你的数据。它会维护详尽的元信息:

  • 表行数统计
  • 不同值的分布
  • 最常见值
  • 数据分布直方图

对于大型表,这些统计基于随机抽样,但总体而言,PostgreSQL对数据特性的把握相当准确。正是基于这些统计信息,查询规划器才能做出明智的执行计划决策。

从简单案例开始

让我们从一个最简单的查询开始:

EXPLAIN SELECT * FROM test WHERE i = 1;
                      QUERY PLAN                      
------------------------------------------------------
 Seq Scan ON test  (cost=0.00..40.00 ROWS=12 width=4)
   FILTER: (i = 1)
(2 ROWS)

这个执行计划包含几个关键元素:

  1. 操作类型:这里是顺序扫描(Seq Scan)
  2. 成本估算:0.00…40.00
  3. 行数估算:12行
  4. 行宽估算:4字节

理解执行计划的结构

执行计划是一个树形结构,每个节点代表一个操作:

  • 上层节点依赖下层节点的数据
  • 每个节点显示其特有的信息
  • 缩进表示操作间的层级关系

在我们的简单例子中,只有一个操作节点(顺序扫描)及其过滤条件。

成本估算的奥秘

成本估算值(cost)可能是最令人困惑的部分。需要明确几点:

  1. 成本不是时间单位,而是一个抽象的相对值

  2. 基于postgresql.conf中的成本参数计算:

    seq_page_cost = 1.0      # 顺序页读取成本
    random_page_cost = 4.0   # 随机页读取成本 
    cpu_tuple_cost = 0.01    # 处理每行的CPU成本
    cpu_index_tuple_cost = 0.005  # 索引扫描的CPU成本
    cpu_operator_cost = 0.0025   # 操作符执行的CPU成本
    

成本值以启动成本..总成本的形式呈现。启动成本是该操作返回第一行前需要的工作量,总成本是返回所有行的总工作量。

索引使用的决策逻辑

考虑这个表和查询:

CREATE TABLE t (
    id serial PRIMARY KEY,
    some_column integer,
    something text
);
CREATE INDEX q ON t(some_column);

EXPLAIN SELECT * FROM t WHERE some_column = 123;

PostgreSQL会根据统计信息决定是否使用索引:

  • 对于小表(如只有几行),顺序扫描通常更快
  • 对于大表且匹配行很少的情况,索引扫描更优
  • 对于大表且匹配行很多的情况,索引扫描反而更慢

执行计划的三种形态

让我们观察同一个查询在不同设置下的执行计划变化:

1.默认情况(使用索引扫描)

EXPLAIN SELECT * FROM test WHERE id = 50;
                              QUERY PLAN                               
-----------------------------------------------------------------------
 INDEX Scan USING test_pkey ON test  (cost=0.28..8.29 ROWS=1 width=36)

2.禁用索引扫描(使用位图扫描)

SET enable_indexscan = false;
EXPLAIN SELECT * FROM test WHERE id = 50;
                               QUERY PLAN                               
------------------------------------------------------------------------
 Bitmap Heap Scan ON test  (cost=4.28..8.30 ROWS=1 width=13)
   ->  Bitmap INDEX Scan ON test_pkey  (cost=0.00..4.28 ROWS=1 width=0)

3. 禁用所有索引相关扫描(退化为顺序扫描)

SET enable_bitmapscan = false;
EXPLAIN SELECT * FROM test WHERE id = 50;
                      QUERY PLAN                      
------------------------------------------------------
 Seq Scan ON test  (cost=0.00..18.50 ROWS=1 width=13)

比较这三种情况的成本估算,可以清楚地看到为什么PostgreSQL默认选择索引扫描——它的总成本最低(8.29)。

实际执行分析

EXPLAIN ANALYZE会实际执行查询并提供真实数据:

EXPLAIN ANALYZE SELECT * FROM t LIMIT 100;
                                                  QUERY PLAN                                                  
--------------------------------------------------------------------------------------------------------------
 LIMIT  (cost=0.00..9.33 ROWS=100 width=608) (actual TIME=0.008..0.152 ROWS=100 loops=1)
   ->  Seq Scan ON t  (cost=0.00..93333.86 ROWS=999986 width=608) (actual TIME=0.007..0.133 ROWS=100 loops=1)
 Total runtime: 0.181 ms

这里新增了四组实际数据:

  1. actual time:实际执行时间(启动…完成)
  2. rows:实际返回行数
  3. loops:该操作被执行次数
  4. Total runtime:查询总耗时

执行计划中的关键陷阱

  1. 多次循环执行:某些操作可能被执行多次,导致总时间远超单次执行时间
  2. 行数估算错误:统计信息不准确会导致糟糕的计划选择
  3. 函数执行特性:某些函数(如PL/pgSQL函数)必须完全执行才能返回结果

总结与展望

理解EXPLAIN输出是优化PostgreSQL查询的基础。本文涵盖了:

  • 执行计划的基本结构
  • 成本估算的含义
  • 索引使用决策逻辑
  • 实际执行数据的解读

在后续文章中,我们将深入探讨:

  1. 各种执行操作(连接、排序等)的工作原理
  2. PostgreSQL统计系统的工作机制
  3. 高级执行计划分析技巧

掌握这些知识将帮助你有效诊断和解决查询性能问题。
关注以下公众号,获取更多PG技术文章。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值