为什么 EXPLAIN 不显示索引扫描的堆提取

Fra*_*k59 5 postgresql performance postgresql-11 postgresql-performance

我试图比较覆盖 b 树索引和简单 b 树索引之间的潜在性能差异,并与EXPLAIN(ANALYZE,BUFFERS)输出混淆。

测试环境

-- function to fill test table
CREATE OR REPLACE FUNCTION fillTable (n INTEGER)
   RETURNS INTEGER AS $rowsCount$
DECLARE
   counter INTEGER := 0 ;
BEGIN

   IF (n < 1) THEN
      RETURN 0 ;
   END IF;

   LOOP
      EXIT WHEN counter = n ;
      counter := counter + 1 ;
      insert into key_value_test(key, value) VALUES (counter,counter);
   END LOOP ;

   return counter;

END ;
$rowsCount$
LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

简单b-tree索引的测试用例

drop table key_value_test;
create table key_value_test
(
    key bigint not null,
    value integer not null,
    test VARCHAR(100),
    CONSTRAINT pk_key_value_test
    PRIMARY KEY(key)
);

SELECT  fillTable(1000000);

vacuum;

EXPLAIN( ANALYZE,BUFFERS ) SELECT  key, value
FROM key_value_test
WHERE  key = 740080;
Run Code Online (Sandbox Code Playgroud)

试运行结果:

Index Scan using pk_key_value_test on key_value_test  (cost=0.42..8.44 rows=1 width=12) (actual time=0.013..0.014 rows=1 loops=1)   
Index Cond: (key = 913680)   
Buffers: shared hit=4 
Planning Time: 0.062 ms 
Execution Time: 0.026 ms
Run Code Online (Sandbox Code Playgroud)

覆盖b-tree索引的测试用例

drop table if exists key_value_test;
create table key_value_test
(
    key bigint not null,
    value integer not null,
    CONSTRAINT pk_key_value_test
      PRIMARY KEY(key) include(value)
);
SELECT  fillTable(1000000);

EXPLAIN( ANALYZE,BUFFERS ) SELECT  key, value
FROM key_value_test
WHERE  key = 740080;

vacuum; 

EXPLAIN( ANALYZE,BUFFERS ) SELECT  key, value
FROM key_value_test
WHERE  key = 740080;
Run Code Online (Sandbox Code Playgroud)

试运行结果

抽真空前:

Index Only Scan using pk_key_value_test on key_value_test  (cost=0.43..8.45 rows=1 width=12) (actual time=0.127..0.128 rows=1 loops=1)
  Index Cond: (key = 740080)
  Heap Fetches: 1
  Buffers: shared hit=4
Planning Time: 0.078 ms
Execution Time: 0.146 ms
Run Code Online (Sandbox Code Playgroud)

抽真空后:

Index Only Scan using pk_key_value_test on key_value_test  (cost=0.42..4.44 rows=1 width=12) (actual time=0.143..0.144 rows=1 loops=1)
  Index Cond: (key = 740080)
  Heap Fetches: 0
  Buffers: shared hit=4
Planning Time: 0.273 ms
Execution Time: 0.156 ms
Run Code Online (Sandbox Code Playgroud)

我的问题是为什么Heap Fetches只对仅索引扫描显示,但对于索引扫描,它的信息是隐藏的?现在我看到我对简单索引和覆盖索引的共享缓冲区有相同的请求计数。那么在这种情况下覆盖索引的性能影响在哪里呢?真的很奇怪,我无法使用 EXPLAIN 来衡量潜在的性能影响。

jja*_*nes 6

索引扫描从堆中获取每一行。这就是使它不是仅索引扫描的原因。仅显示索引扫描的计数才有意义,因为这是提供信息的唯一情况。

“缓冲区:”这一行通常可能提供更多信息(对于您有不止一行处于危险之中的现实情况)。但是对于您的 IOS,该行在两种情况下都显示 4。没有改变的原因是在清理之前,表没有可见性映射,因此没有要读入缓冲区的页面。它意识到虚拟机不存在,而是点击表格页面。清空后,它会访问(现在存在的)vm 页面,这告诉我们不需要访问表。所以你总是得到 4 个缓冲区命中,3 个用于索引,1 个用于 vm 或表。

如果您要部分填充表,然后抽真空以使 vm 存在,然后插入另一条记录,然后查询该记录,您将看到 IOS 现在命中了 5 个缓冲区。3 表示索引,1 表示可见性地图页面,表示该页面并非全部可见,1 表示表。

如果您有多次命中索引的大型查询,那么解释缓冲区命中计数会变得非常棘手。在单个查询执行中,它会在最近使用的可见性地图页面上保留一个图钉。在您仍然按住大头针的情况下重复点击同一页面不会算作新的缓冲区点击。仅当您需要切换到 vm 中的不同页面时,它才算作新的缓冲区命中。

所有这些都不太可能非常有效。在真实硬件上构建真实大小的真实数据集,并对其运行真实查询,并测量实际性能。一旦您有需要解释的合理性能差异,您可以深入了解细节以找到解释。但是试图从一行查询中推断出来并不是很有用。