跟踪 PostgreSQL 查询的处理过程

John Doe 五月 14, 2024

摘要:在本文中,我们将跟踪 PostgreSQL 查询的处理过程。

目录

带有 ORDER BY 子句的聚合表达式

考虑一个简单的表,带有一个 int 列,其中包含了 3 行记录,范围从 1 到 3:

CREATE TABLE foo AS
  SELECT * FROM generate_series(1, 3) i;

SELECT * FROM foo;
 i
---
 1
 2
 3
(3 rows)

现在让我们假设,我们想将这些行转换为 json 数组。这可以使用 json_agg 聚集函数来实现:

SELECT json_agg(i) FROM foo;
 json_agg
-----------
 [1, 2, 3]
(1 row)

但是,如果我们想按降序对数组元素进行排序呢?没问题,我们可以简单地使用所有聚合表达式支持的 order by 子句:

SELECT json_agg(i ORDER BY i DESC) FROM foo;
 json_agg
-----------
 [3, 2, 1]
(1 row)

很可能,您会想象这个查询的EXPLAIN输出包含三个节点:一个Seq Scan,一个Sort和一个Aggregate。然而,当我们运行EXPLAIN时,结果是没有Sort节点。

EXPLAIN SELECT json_agg(i ORDER BY i DESC) FROM foo;
                         QUERY PLAN
-------------------------------------------------------------
 Aggregate  (cost=41.88..41.89 rows=1 width=32)
   ->  Seq Scan on foo  (cost=0.00..35.50 rows=2550 width=4)
(2 rows)

事实上,即使我们更改排序顺序,或完全删除 order by 子句,该计划也依然保持不变:

EXPLAIN SELECT json_agg(i ORDER BY i ASC) FROM foo;
                         QUERY PLAN
-------------------------------------------------------------
 Aggregate  (cost=41.88..41.89 rows=1 width=32)
   ->  Seq Scan on foo  (cost=0.00..35.50 rows=2550 width=4)
(2 rows)

EXPLAIN SELECT json_agg(i) FROM foo;
                         QUERY PLAN
-------------------------------------------------------------
 Aggregate  (cost=41.88..41.89 rows=1 width=32)
   ->  Seq Scan on foo  (cost=0.00..35.50 rows=2550 width=4)
(2 rows)

但这怎么可能?如果没有Sort节点,聚合将如何进行排序?

跟踪查询处理

实际上,聚合计划节点执行了它们自己的排序。这些排序不会显式地显示在EXPLAIN中,但是我们可以使用 debug_print_plan 开发者选项,来跟踪它们。

SET debug_print_parse = on;
SET debug_print_rewritten = on;
SET debug_print_plan = on;
SET debug_pretty_print = on;

为什么在跟踪 PostgreSQL 查询处理时要启用调试日志?这是因为查询处理的某些阶段的输出,在内部会表示为复杂的结构列表,除非我们编写了可以帮助我们递归打印复杂结构内容的第三方打印脚本,否则打印该结构不是很简单。PostgreSQL 已经内置了这个函数,并以调试日志的形式呈现。

具体来说,您需要查找下面的:aggorder字段:

SELECT json_agg(i ORDER BY i DESC) FROM foo;

LOG:  plan:
DETAIL:     {PLANNEDSTMT
   :commandType 1
   :queryId 0
   :hasReturning false
   :hasModifyingCTE false
   :canSetTag true
   :transientPlan false
   :dependsOnRole false
   :parallelModeNeeded false
   :planTree
      {AGG
      :startup_cost 41.88
      :total_cost 41.89
      :plan_rows 1
      :plan_width 32
      :parallel_aware false
      :plan_node_id 0
      :targetlist (
         {TARGETENTRY
         :expr
            {AGGREF
            :aggfnoid 3175
            :aggtype 114
            :aggcollid 0
            :inputcollid 0
            :aggtranstype 2281
            :aggargtypes (o 23)
            :aggdirectargs <>
            :args (
               {TARGETENTRY
               :expr
                  {VAR
                  :varno 65001
                  :varattno 1
                  :vartype 23
                  :vartypmod -1
                  :varcollid 0
                  :varlevelsup 0
                  :varnoold 1
                  :varoattno 1
                  :location 16
                  }
               :resno 1
               :resname <>
               :ressortgroupref 1
               :resorigtbl 0
               :resorigcol 0
               :resjunk false
               }
            )
            :aggorder (
               {SORTGROUPCLAUSE
               :tleSortGroupRef 1
               :eqop 96
               :sortop 521
               :nulls_first true
               :hashable true
               }
            )
            :aggdistinct <>
            :aggfilter <>
            :aggstar false
            :aggvariadic false
            :aggkind n
            :agglevelsup 0
            :aggsplit 0
            :location 7
            }
         :resno 1
         :resname json_agg
         :ressortgroupref 0
         :resorigtbl 0
         :resorigcol 0
         :resjunk false
         }
      )
      :qual <>
      :lefttree
         {SEQSCAN
         :startup_cost 0.00
         :total_cost 35.50
         :plan_rows 2550
         :plan_width 4
         :parallel_aware false
         :plan_node_id 1
         :targetlist (
            {TARGETENTRY
            :expr
               {VAR
               :varno 1
               :varattno 1
               :vartype 23
               :vartypmod -1
               :varcollid 0
               :varlevelsup 0
               :varnoold 1
               :varoattno 1
               :location -1
               }
            :resno 1
            :resname <>
            :ressortgroupref 0
            :resorigtbl 0
            :resorigcol 0
            :resjunk false
            }
         )
         :qual <>
         :lefttree <>
         :righttree <>
         :initPlan <>
         :extParam (b)
         :allParam (b)
         :scanrelid 1
         }
      :righttree <>
      :initPlan <>
      :extParam (b)
      :allParam (b)
      :aggstrategy 0
      :aggsplit 0
      :numCols 0
      :grpColIdx
      :grpOperators
      :numGroups 1
      :aggParams (b)
      :groupingSets <>
      :chain <>
      }
   :rtable (
      {RTE
      :alias <>
      :eref
         {ALIAS
         :aliasname foo
         :colnames ("i")
         }
      :rtekind 0
      :relid 404407
      :relkind r
      :tablesample <>
      :lateral false
      :inh false
      :inFromCl true
      :requiredPerms 2
      :checkAsUser 0
      :selectedCols (b 9)
      :insertedCols (b)
      :updatedCols (b)
      :securityQuals <>
      }
   )
   :resultRelations <>
   :utilityStmt <>
   :subplans <>
   :rewindPlanIDs (b)
   :rowMarks <>
   :relationOids (o 404407)
   :invalItems <>
   :nParamExec 0
}

 json_agg
-----------
 [3, 2, 1]
(1 row)

综上所述:虽然EXPLAIN对于那些寻求了解查询性能的人来说是一个强大的武器,但您应该意识到,有些事情是您无法用EXPLAIN来分析的。