• postgresql explain的初步分析


    os: centos 7.4
    postgresql: 9.6.8

    explain 是 postgresql 查看执行计划最直接的方式。

    explain 语法

    EXPLAIN [ ( option [, ...] ) ] statement
    EXPLAIN [ ANALYZE ] [ VERBOSE ] statement
    
    这里 option可以是:
    
        ANALYZE [ boolean ]
        VERBOSE [ boolean ]
        COSTS [ boolean ]
        BUFFERS [ boolean ]
        TIMING [ boolean ]
        FORMAT { TEXT | XML | JSON | YAML }

    explain 查看

    现在有 user,role,user_role 三个表的关联,来获取登录用户的角色权限。
    如下

    mondb=# explain
    SELECT tu.user_id,
           tu.user_first_name::text || tu.user_last_name::text AS user_name,
           tr.role_id,
           tr.role_name
      FROM t_base_user tu,
           t_base_role tr,
           t_base_user_role tur
     WHERE 1 = 1 
       AND tu.user_id = tur.user_id 
       AND tr.role_id = tur.role_id 
       AND tu.user_status = '1' 
       AND tr.role_status = '1'
     ORDER BY tr.role_id, 
              tu.user_id;
    
                                          QUERY PLAN                                       
    ---------------------------------------------------------------------------------------
     Sort  (cost=3.94..3.97 rows=9 width=57)
       Sort Key: tr.role_id, tu.user_id
       ->  Hash Join  (cost=2.41..3.80 rows=9 width=57)
             Hash Cond: ((tur.role_id)::text = (tr.role_id)::text)
             ->  Hash Join  (cost=1.24..2.48 rows=9 width=19)
                   Hash Cond: ((tur.user_id)::text = (tu.user_id)::text)
                   ->  Seq Scan on t_base_user_role tur  (cost=0.00..1.11 rows=11 width=9)
                   ->  Hash  (cost=1.12..1.12 rows=9 width=16)
                         ->  Seq Scan on t_base_user tu  (cost=0.00..1.12 rows=9 width=16)
                               Filter: ((user_status)::text = '1'::text)
             ->  Hash  (cost=1.09..1.09 rows=7 width=19)
                   ->  Seq Scan on t_base_role tr  (cost=0.00..1.09 rows=7 width=19)
                         Filter: ((role_status)::text = '1'::text)
    (13 rows)
    
    mondb=# 
    

    以这一行为例:-> Seq Scan on t_base_user_role tur (cost=0.00..1.11 rows=11 width=9) ,说下每行后面类似 (cost=0.00..1.11 rows=11 width=9) 所代表的含义。
    有这个输出是因为 option 的 COSTS 默认为 true,即 explain 等价于 explain (COSTS true)

    那这个 COSTS 的具体含义是:包括每一个计划结点的估计启动和总代价,以及估计的行数和每行的宽度。这个参数默认被设置为TRUE。

    显示中最重要的部分是估计出的语句执行代价,它是计划器对于该语句要运行多久的猜测(以任意的代价单位度量,但是习惯上表示取磁盘页面的次数)。
    事实上会显示两个数字:在第一行能被返回前的启动代价,以及返回所有行的总代价。
    对于大部分查询来说总代价是最重要的,但是在一些情景中(如EXISTS中的子查询),计划器将选择更小的启动代价来代替最小的总代价(因为因为执行器将在得到一行后停止)。此外,如果你用一个LIMIT子句限制返回行的数量,计划器会在终端代价之间做出适当的插值来估计到底哪个计划是真正代价最低的。

    cost

    总时间(单位:毫秒)
    cost=0.00..1.11 是怎么计算出来的?估计很多人都想搞清楚。
    其中:
    0.00代表估计的启动开销。在输出阶段可以开始之前消耗的时间,例如在一个排序结点里执行排序的时间。
    1.11代表估计的总开销。这个估计值基于的假设是计划结点会被运行到完成,即所有可用的行都被检索。实际上一个结点的父结点可能很快停止读所有可用的行

    这里涉及了postgresql 几个参数:

    seq_page_cost:       连续块扫描操作的单个块的cost. 例如全表扫描
    random_page_cost:    随机块扫描操作的单个块的cost. 例如索引扫描
    cpu_tuple_cost:      处理每条记录的CPU开销(tuple:关系中的一行记录)
    cpu_index_tuple_cost:扫描每个索引条目带来的CPU开销
    cpu_operator_cost:   操作符或函数带来的CPU开销.(需要注意函数以及操作符对应的函数的三态, 执行计划会根据三态做优化, 关系到多条记录时三态对应的调用次数是需要关心的)

    查看当前设置

    mondb=# select name,setting from pg_settings ps
      where ps.name in ('seq_page_cost','random_page_cost','cpu_tuple_cost','cpu_index_tuple_cost','cpu_operator_cost');
    
             name         | setting 
    ----------------------+---------
     cpu_index_tuple_cost | 0.005
     cpu_operator_cost    | 0.0025
     cpu_tuple_cost       | 0.01
     random_page_cost     | 4
     seq_page_cost        | 1
    (5 rows)

    总成本=seq_page_cost*relpages+cpu_tuple_cost*reltuples

    mondb=# select relpages,reltuples from pg_class where relname='t_base_user_role';
     relpages | reltuples 
    ----------+-----------
            1 |        11
    (1 row)
    

    带入公式:

    1(seq_page_cost)*1(relpages)+0.01(cpu_tuple_cost)*11(reltuples)=1.11

    有过滤条件的公式,比如 >=,多了一个 cpu_operation_cost 的成本。
    1(seq_page_cost)*1(relpages)+0.01(cpu_tuple_cost)*11(reltuples)+0.0025(cpu_operation_cost)*11(reltuples)

    rows

    计划结点输出行数的估计值。同样,也假定该结点能运行到完成。
    rows=11

    mondb=# select reltuples from pg_class where relname='t_base_user_role';
     reltuples 
    -----------
            11
    (1 row)

    width

    计划结点输出的行平均宽度(以字节计算)
    width=9

    
    mondb=# select sum(length(user_id)),
           sum(length(role_id)),
           (sum(length(user_id)) + sum(length(role_id))) /count(1)
      from t_base_user_role;
    
     sum | sum | ?column? 
    -----+-----+----------
      59 |  30 |        8
    (1 row)
    

    explain analyze

    analyze 代表语句真实执行了。所以会有类似 (actual time=0.263..0.265 rows=7 loops=1)的输出

    mondb=# explain analyze
    SELECT tu.user_id,
           tu.user_first_name::text || tu.user_last_name::text AS user_name,
           tr.role_id,
           tr.role_name
      FROM t_base_user tu,
           t_base_role tr,
           t_base_user_role tur
     WHERE 1 = 1 
       AND tu.user_id = tur.user_id 
       AND tr.role_id = tur.role_id 
       AND tu.user_status = '1' 
       AND tr.role_status = '1'
     ORDER BY tr.role_id, 
              tu.user_id;
    
                                                                QUERY PLAN                                                            
    ----------------------------------------------------------------------------------------------------------------------------------
     Sort  (cost=3.94..3.97 rows=9 width=57) (actual time=0.263..0.265 rows=7 loops=1)
       Sort Key: tr.role_id, tu.user_id
       Sort Method: quicksort  Memory: 25kB
       ->  Hash Join  (cost=2.41..3.80 rows=9 width=57) (actual time=0.194..0.214 rows=7 loops=1)
             Hash Cond: ((tur.role_id)::text = (tr.role_id)::text)
             ->  Hash Join  (cost=1.24..2.48 rows=9 width=19) (actual time=0.063..0.076 rows=7 loops=1)
                   Hash Cond: ((tur.user_id)::text = (tu.user_id)::text)
                   ->  Seq Scan on t_base_user_role tur  (cost=0.00..1.11 rows=11 width=9) (actual time=0.010..0.011 rows=11 loops=1)
                   ->  Hash  (cost=1.12..1.12 rows=9 width=16) (actual time=0.029..0.029 rows=7 loops=1)
                         Buckets: 1024  Batches: 1  Memory Usage: 9kB
                         ->  Seq Scan on t_base_user tu  (cost=0.00..1.12 rows=9 width=16) (actual time=0.013..0.020 rows=7 loops=1)
                               Filter: ((user_status)::text = '1'::text)
             ->  Hash  (cost=1.09..1.09 rows=7 width=19) (actual time=0.063..0.063 rows=7 loops=1)
                   Buckets: 1024  Batches: 1  Memory Usage: 9kB
                   ->  Seq Scan on t_base_role tr  (cost=0.00..1.09 rows=7 width=19) (actual time=0.034..0.041 rows=7 loops=1)
                         Filter: ((role_status)::text = '1'::text)
     Planning time: 0.607 ms
     Execution time: 0.351 ms
    (18 rows)
    
    

    explain (analyze true)

    mondb=# explain (analyze true)
    SELECT tu.user_id,
           tu.user_first_name::text || tu.user_last_name::text AS user_name,
           tr.role_id,
           tr.role_name
      FROM t_base_user tu,
           t_base_role tr,
           t_base_user_role tur
     WHERE 1 = 1 
       AND tu.user_id = tur.user_id 
       AND tr.role_id = tur.role_id 
       AND tu.user_status = '1' 
       AND tr.role_status = '1'
     ORDER BY tr.role_id, 
              tu.user_id;
    
                                                                QUERY PLAN                                                            
    ----------------------------------------------------------------------------------------------------------------------------------
     Sort  (cost=3.94..3.97 rows=9 width=57) (actual time=0.120..0.120 rows=7 loops=1)
       Sort Key: tr.role_id, tu.user_id
       Sort Method: quicksort  Memory: 25kB
       ->  Hash Join  (cost=2.41..3.80 rows=9 width=57) (actual time=0.077..0.091 rows=7 loops=1)
             Hash Cond: ((tur.role_id)::text = (tr.role_id)::text)
             ->  Hash Join  (cost=1.24..2.48 rows=9 width=19) (actual time=0.031..0.040 rows=7 loops=1)
                   Hash Cond: ((tur.user_id)::text = (tu.user_id)::text)
                   ->  Seq Scan on t_base_user_role tur  (cost=0.00..1.11 rows=11 width=9) (actual time=0.004..0.005 rows=11 loops=1)
                   ->  Hash  (cost=1.12..1.12 rows=9 width=16) (actual time=0.016..0.016 rows=7 loops=1)
                         Buckets: 1024  Batches: 1  Memory Usage: 9kB
                         ->  Seq Scan on t_base_user tu  (cost=0.00..1.12 rows=9 width=16) (actual time=0.007..0.011 rows=7 loops=1)
                               Filter: ((user_status)::text = '1'::text)
             ->  Hash  (cost=1.09..1.09 rows=7 width=19) (actual time=0.030..0.030 rows=7 loops=1)
                   Buckets: 1024  Batches: 1  Memory Usage: 9kB
                   ->  Seq Scan on t_base_role tr  (cost=0.00..1.09 rows=7 width=19) (actual time=0.015..0.021 rows=7 loops=1)
                         Filter: ((role_status)::text = '1'::text)
     Planning time: 0.443 ms
     Execution time: 0.187 ms
    (18 rows)
    
    

    从输出上来看 explain analyze 等价于 explain (analyze true),那为什么会有两种表现形式?很容易让人困惑。

    对于 EXPLAIN [ ANALYZE ] [ VERBOSE ] statement ,只有 ANALYZE 和 VERBOSE 选项能被指定,并且必须按照上述的顺序,不要把选项列表放在圆括号内。在PostgreSQL 9.0 之前,只支持没有圆括号的语法。
    我们期望所有新的选项将只在圆括号语法中支持。

    个人感觉没有放到圆括号的 analyze 是为了兼容之前的老版本。

    官方文档要好好读!!!
    官方文档要好好读!!!
    官方文档要好好读!!!

    参考
    http://www.postgres.cn/docs/9.6/sql-explain.html
    http://www.postgres.cn/docs/9.6/using-explain.html

  • 相关阅读:
    1451. Rearrange Words in a Sentence
    1450. Number of Students Doing Homework at a Given Time
    1452. People Whose List of Favorite Companies Is Not a Subset of Another List
    1447. Simplified Fractions
    1446. Consecutive Characters
    1448. Count Good Nodes in Binary Tree
    709. To Lower Case
    211. Add and Search Word
    918. Maximum Sum Circular Subarray
    lua 时间戳和时间互转
  • 原文地址:https://www.cnblogs.com/ctypyb2002/p/9792907.html
Copyright © 2020-2023  润新知