详解 PostgreSQL explain 查询计划

  • 除第一行以外每个->表示一个子动作

  • 执行计划的阅读顺序都是从后至前

  • width=0是表示只获取行的位置,没有读取数据.开始读数据后width肯定大于0

EXPLAIN用于理解和优化SQL查询,它接受SELECT …,UPDATE …或DELETE …之类的sql命令并返回查询计划,查询计划详细说明了执行sql所采取的方法.

以下结合实例讲解一个执行计划:

EXPLAIN ANALYZE SELECT *
FROM tenk1 t1, tenk2 t2
WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2 ORDER BY t1.fivethous;

                                                                 QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=717.34..717.59 rows=101 width=488) (actual time=7.761..7.774 rows=100 loops=1)
   Sort Key: t1.fivethous
   Sort Method: quicksort  Memory: 77kB
   ->  Hash Join  (cost=230.47..713.98 rows=101 width=488) (actual time=0.711..7.427 rows=100 loops=1)
         Hash Cond: (t2.unique2 = t1.unique2)
         ->  Seq Scan on tenk2 t2  (cost=0.00..445.00 rows=10000 width=244) (actual time=0.007..2.583 rows=10000 loops=1)
         ->  Hash  (cost=229.20..229.20 rows=101 width=244) (actual time=0.659..0.659 rows=100 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 28kB
               ->  Bitmap Heap Scan on tenk1 t1  (cost=5.07..229.20 rows=101 width=244) (actual time=0.080..0.526 rows=100 loops=1)
                     Recheck Cond: (unique1 < 100)
                     ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..5.04 rows=101 width=0) (actual time=0.049..0.049 rows=100 loops=1)
                           Index Cond: (unique1 < 100)
 Planning time: 0.194 ms
 Execution time: 8.008 ms

PostgreSQL建立了一个计划节点的树状结构,代表所采取的不同动作,除第一行以外每个->表示一个子动作,因此上文的查询结构为:

Sort
└── Hash Join
    ├── Seq Scan
    └── Hash
        └── Bitmap Heap Scan
            └── Bitmap Index Scan

每个节点表示子动作,阅读顺序从后至前。因此上面的执行计划第一步是在tenk_unique1索引上进行Bitmap Index Scan.

Step 1

Bitmap Index Scan on tenk1_unique1  (cost=0.00..5.04 rows=101 width=0) (actual time=0.049..0.049 rows=100 loops=1)
      Index Cond: (unique1 < 100)
  • 对应于SQL中的”WHERE t1.unique1<100”.PostgreSQL匹配索引条件”unique1<100”的行,行本身不会在这里返回(width=0).
  • 成本(cost)估算(cost=0.00..5.04 rows=101 width=0)中的”cost=0.00..5.04”意味着PostgreSQL希望花费”5.04”的任意计算单位来找到这些值,”0.00”是该节点起始工作成本(只是启动成本).”rows=101”是此索引扫描将返回的估计行数.width是返回行的估计大小(以字节为单位),”width=0”是因为此处只获取数据的实际位置(ctid)而不是返回行的内容.
  • 因为使用ANALYZE选项运行EXPLAIN,所以查询实际上已执行并且捕获了时间信息.”(actual time=0.049..0.049 rows=100 loops=1)”,”loops=1”表示索引扫描被执行了1次;”rows=100”表示返回了100行;”actual time=0.049..0.049”表示此步骤的开始时间是0.049,结束时间0.049,单位为毫秒,因此此实际执行时间是0,实际时间是每次迭代的平均值,可以将值乘以循环次数以获得真实的执行时间.
  • 成本或时间范围值可能不同,因此给出了最小/最大时间的概念,上例中查询的每单位成本比率为 “0.049ms/5.04成本单位≈0.01ms/每个成本单位”,注意是actual time的结束时间/cost结束成本。

Step 2

Bitmap Heap Scan on tenk1 t1  (cost=5.07..229.20 rows=101 width=244) (actual time=0.080..0.526 rows=100 loops=1)
      Recheck Cond: (unique1 < 100)
  • Step 1索引扫描的结果传递给”Bitmap Heap Scan”操作.在这个节点中,PostgreSQL取得tenk1表(别名为t1)中行的位置,”unique1 < 100”从表中取出行.
  • 可以看到成本预期乘以Step 1计算的值0.01,意味着大概的预期时间(229.20-5.07) * 0.01≈2.24ms ,但是实际时间为0.526ms每行,实际时间是大概的预期时间1/4,这可能是因为成本估算值是一个上限值,并非所有行都需要读取,或者因为重新检查条件始终为真。
  • Bitmap Index Scan和Bitmap Heap Scan的组合比从表中顺序读取行(Seq Scan)要昂贵得多,但由于在这种情况下需要访问的行相对较少,所以速度更快.
  • 通过在提取这些行之前将这些行排序为物理顺序可以进一步提高速度,从而最大限度地降低单独提取的成本.

Step 3

Hash  (cost=229.20..229.20 rows=101 width=244) (actual time=0.659..0.659 rows=100 loops=1)
      Buckets: 1024  Batches: 1  Memory Usage: 28kB
  • 堆扫描Step 2(Heap Scan)的结果中,来自表tenk1且”unique1<100”的行会在读取时插入到内存哈希表中,正如我们从成本上看到的那样,这个步骤完全没有占用运行时间(cost=229.20..229.20)(actual time=0.659..0.659)。
  • Hash包含Buckets和Batches以及内存使用情况的信息.如果Batches>1,则还涉及磁盘使用情况,本例中无.内存使用情况通过公式大约为”100*244=24.4kB”,接近实际使用的内存28kB,多出来的部分假设为Hash密钥本身所占用的内存.

Step 4

->  Hash Join  (cost=230.47..713.98 rows=101 width=488) (actual time=0.711..7.427 rows=100 loops=1)
      Hash Cond: (t2.unique2 = t1.unique2)
      ->  Seq Scan on tenk2 t2  (cost=0.00..445.00 rows=10000 width=244) (actual time=0.007..2.583 rows=10000 loops=1)
  • PostgreSQL从tenk2中读取所有10000行(别名为t2),并根据tenk1的Hash来检查它们.Hash Join意味着一个表的行被输入到内存中的Hash,之后扫描另一个表的行,并根据Hash探测匹配的值,在第二行为匹配条件”Hash Cond: (t2.unique2 = t1.unique2)”.
  • 请注意,因为查询选择了tenk1和tenk2中的所有值,所以在哈希连接期间每行的宽度都会增加一倍。
  • 注意观察,在本示例中执行时间大部份花在这上步骤上(actual time=0.711..7.427).Step 4花费了7.427ms,占总执行时间的7.427/7.774=95%.

Step 5

Sort  (cost=717.34..717.59 rows=101 width=488) (actual time=7.761..7.774 rows=100 loops=1)
  Sort Key: t1.fivethous
  Sort Method: quicksort  Memory: 77kB
  • 所有符合条件的行都已经收集完毕,然后通过Sort Key: t1.fivethous排序结果集.
  • 排序的算法为quicksort,Memory表示排序是在内存上完成的,使用了77kB大小的内存.当排序所需的内存超过work_mem参数的大小时,将使用磁盘空间,这将极大影响查询速度
  • 排序使用磁盘空间时需要增加work_mem大小,以避免使用磁盘空间
  • 注意排序是在内存和磁盘上完成的,优先使用内存,因此实际所需空间大小=所需的内存/所需的磁盘空间
  • 这个查询所需要的时间大约为7.774ms,符合Execution time: 8.008 ms的时间

猜你喜欢

转载自blog.csdn.net/kmblack1/article/details/80761647