ePQ支持查看与分析执行计划

PolarDB PostgreSQL版支持ePQ查看与分析执行计划。

背景信息

PostgreSQL提供了EXPLAIN命令用于SQL语句的性能分析,该命令可以输出SQL对应的查询计划,以及在执行过程中的具体耗时、资源消耗等信息,可用于排查SQL的性能瓶颈。

EXPLAIN命令只适用于单机执行的SQL性能分析,PolarDB PostgreSQL版的ePQ扩展了EXPLAIN的功能,使其可以打印ePQ的跨机并行执行计划,还可以统计ePQ执行计划在各个算子上的执行时间、数据扫描量、内存使用等信息,并以统一的视角提供给用户。

前提条件

支持的PolarDB PostgreSQL版的版本如下:

  • PostgreSQL 11(内核小版本1.1.22及以上)

  • PostgreSQL 14(内核小版本14.6.6.0及以上)

说明

您可通过如下语句查看PolarDB PostgreSQL版的内核小版本的版本号:

  • PostgreSQL 11

    show polar_version;
  • PostgreSQL 14

    select version();

原理介绍

ePQ查询的发起进程(QC)与工作进程(Worker)之间采用libpqY协议进行通信:

  • QC将EXPLAIN ANALYZE命令下发给Worker。

  • 各个Worker进程统计本进程内的资源使用、执行耗时等信息。

  • 各个Worker在完成计划分片的执行后,将统计信息发送给QC。

  • QC等待所有Worker进程完成各自执行后,进行统计计算并输出。

image

功能介绍

执行计划查看

ePQ的执行计划是分片的。每个计划分片(Slice)由计算节点上的虚拟执行单元(Segment)启动的一组进程(Gang)负责执行,完成SQL的一部分计算。ePQ在执行计划中引入了Motion算子,用于在执行不同计划分片的进程组之间进行数据传递。因此,Motion算子是计划分片的边界。

ePQ中总共引入了三种Motion算子:

  • PX Coordinator:源端数据发送到同一个目标端(汇聚)。

  • PX Broadcast:源端数据发送到每一个目标端(广播)。

  • PX Hash:源端数据经过哈希计算后发送到某一个目标端(重分布)。

示例

  • 以下为一个简单查询示例:

    CREATE TABLE t (id INT);
    SET polar_enable_px TO ON;
    EXPLAIN (COSTS OFF) SELECT * FROM t LIMIT 1;
                       QUERY PLAN
    -------------------------------------------------
     Limit
       ->  PX Coordinator 6:1  (slice1; segments: 6)
             ->  Partial Seq Scan on t
     Optimizer: PolarDB PX Optimizer
    (4 rows)

    以上执行计划以Motion算子为界,被分为了两个分片:一个是接收最终结果的分片slice0,一个是扫描数据的分片slice1

    对于slice1计划分片,ePQ将使用六个执行单元(segments: 6)分别启动一个进程来执行,这六个进程各自负责扫描表的一部分数据(Partial Seq Scan),通过Motion算子将六个进程的数据汇聚到一个目标端(PX Coordinator 6:1),传递给Limit算子。

  • 如果查询逐渐复杂,则执行计划中的计划分片和Motion算子会越来越多,示例如下:

    CREATE TABLE t1 (a INT, b INT, c INT);
    SET polar_enable_px TO ON;
    EXPLAIN (COSTS OFF) SELECT SUM(b) FROM t1 GROUP BY a LIMIT 1;
                             QUERY PLAN
    ------------------------------------------------------------
     Limit
       ->  PX Coordinator 6:1  (slice1; segments: 6)
             ->  GroupAggregate
                   Group Key: a
                   ->  Sort
                         Sort Key: a
                         ->  PX Hash 6:6  (slice2; segments: 6)
                               Hash Key: a
                               ->  Partial Seq Scan on t1
     Optimizer: PolarDB PX Optimizer
    (10 rows)

    以上执行计划中总共有三个计划分片。将会有六个进程(segments: 6)负责执行slice2分片,分别扫描表的一部分数据,然后通过Motion算子(PX Hash 6:6)将数据重分布到另外六个(segments: 6)负责执行slice1分片的进程上,各自完成排序(Sort)和聚合(GroupAggregate),最终通过Motion算子(PX Coordinator 6:1)将数据汇聚到结果分片slice0

执行计划分析

EXPLAIN中的ANALYZE选项会使查询被真正执行,并收集执行过程中的各项统计信息,而不仅仅是打印执行计划。在ePQ的执行计划中,同一个算子会被一组进程执行。因此,ePQ的EXPLAIN ANALYZE需要收集执行同一个算子的所有进程的统计信息。

算子级别的统计信息如下:

  • 算子执行时间:取执行该算子的所有进程的最长执行时间。

  • 算子扫描总行数:取执行该算子的所有进程的扫描行数累加和。

  • 算子执行次数(循环):取执行该算子的所有进程的循环次数累加和。

  • 算子的资源使用信息:取执行该算子的所有进程的资源使用量累加和。

此外,ePQ的EXPLAIN ANALYZE还可以收集执行同一算子的各进程级别的统计信息,可被用于判断计划执行过程中是否存在倾斜。具体包含信息如下:

  • 每个进程的内存使用情况。

  • 每个进程的执行时间。

  • 每个进程的处理行数。

示例

  1. 创建一张表并插入数据。

    CREATE TABLE t2 (a INT, b INT, c VARCHAR(20));
    INSERT INTO t2 SELECT i, i*2, to_char(i, 'FM00000') FROM generate_series(1, 100000) i;
  2. 设置如下参数并执行EXPLAIN ANALYZE

    SET polar_enable_px TO ON;
    SET polar_px_enable_explain_all_stat TO ON;
    SET polar_px_explain_memory_verbosity TO detail;
    EXPLAIN (COSTS OFF, ANALYZE) SELECT * FROM t2;
                                            QUERY PLAN
    -------------------------------------------------------------------------------------------
     PX Coordinator 6:1  (slice1; segments: 6) (actual time=0.816..54.225 rows=100000 loops=1)
       Executor Memory: 9kB  Workers: 1  Max: 9kB (worker -1)
       ->  Partial Seq Scan on t2 (actual time=0.052..24.732 rows=94720 loops=1)
             Executor Memory: 326kB  Workers: 6  Max: 145kB (worker 1)
             allstat:
                 worker:0, first_time:7.396(ms), total_time:25(ms), total_num:94720
                 worker:1, first_time:7.396(ms), total_time:2.819(ms), total_num:5280
                 worker:2, first_time:7.393(ms), total_time:0.074(ms), total_num:0
                 worker:3, first_time:7.400(ms), total_time:0.078(ms), total_num:0
                 worker:4, first_time:7.402(ms), total_time:0.086(ms), total_num:0
                 worker:5, first_time:7.399(ms), total_time:0.098(ms), total_num:0
             Dynamic Pages Per Worker: [512,29]
     Planning Time: 9.768 ms
     Optimizer: PolarDB PX Optimizer
       (slice0)    Executor memory: 38K bytes.
       (slice1)    Executor memory: 68K bytes avg x 6 workers, 164K bytes max (seg1).
     Execution Time: 65.572 ms
    (17 rows)

    在上述执行计划中:

    • 每个算子下的Executor Memory部分打印了执行这个算子的所有进程所使用的总内存量、进程数量以及使用内存量最大的进程编号。

    • allstat部分打印了执行算子的每一个进程的准备时间(first_time)、执行时间(total_time)和处理元组数量(total_num)。