调整设置以减少 postgres 中大型查询的读取块数

Gui*_*mas 6 postgresql performance postgresql-performance

使用 postgres 9.6,我不明白共享缓冲区如何与索引一起工作。

设置

  • Postgres 9.6
  • 所有默认设置
    • 共享缓冲区:128Mb
    • 工作内存:4Mb
    • 块大小:8192
    • ...

这意味着 shared_buffer 大小为 128 * 1024 * 1024 / 8192 = 16384 个块。

测试数据

我创建了一个简单的表,其中包含随机数据和每列的索引。

DROP TABLE IF EXISTS sandbox;


CREATE TABLE sandbox AS
SELECT generate_series(1, 4000000) AS pk,
       random() AS x;

CREATE INDEX ON sandbox(pk);
CREATE INDEX ON sandbox(x);
Run Code Online (Sandbox Code Playgroud)

我去了 4M 行。这是表和索引占用的大小:

postgres=# SELECT relname AS "relation",
       pg_relation_size(C.oid) / 8192 AS "blocks",
       pg_size_pretty(pg_relation_size(C.oid)) AS "size"
FROM pg_class C
LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
WHERE relname = 'sandbox' ;
 relation | blocks |  size  
----------+--------+--------
 sandbox  |  21622 | 169 MB
Run Code Online (Sandbox Code Playgroud)

所以表大小 (169 Mb) 大于共享缓冲区 (128Mb)

解释和分析

我想检索条件为 x 的行。这将提取约 2%的数据。

postgres=# explain (ANALYZE, buffers, format text)
SELECT *
FROM sandbox
WHERE x < .02;
                                                              QUERY PLAN                                                              
--------------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on sandbox  (cost=24961.76..63250.42 rows=1333333 width=12) (actual time=29.059..347.203 rows=80218 loops=1)
   Recheck Cond: (x < '0.02'::double precision)
   Heap Blocks: exact=21113
   Buffers: shared read=21335
   ->  Bitmap Index Scan on sandbox_x_idx  (cost=0.00..24628.43 rows=1333333 width=0) (actual time=20.458..20.458 rows=80218 loops=1)
         Index Cond: (x < '0.02'::double precision)
         Buffers: shared read=222
 Planning time: 0.293 ms
 Execution time: 573.949 ms
Run Code Online (Sandbox Code Playgroud)

可以看出:

  • Bitmap Index Scan on sandbox_x_idx指示
    • 成本部分的行 = 1333333。所以是桌子大小的 1/3。那个数字是什么意思?
    • rows=80218 代表表大小的 2%。
  • 堆块等于 21113。这大约是 164Mb,所以它存储在堆中的所有表对吗?尽管有索引,为什么他必须遍历所有表?
  • 不使用缓冲区,因为只有read(来自磁盘)和没有hits(来自内存)。即使我重新运行查询,我也会得到相同的行为

现在我在 pk 上运行一个查询,我仍然获取 ~2% 的数据:

postgres=# explain (ANALYZE, buffers, format text)
SELECT *
FROM sandbox
WHERE pk < 80000;
                                                              QUERY PLAN                                                               
---------------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on sandbox  (cost=24961.76..63250.42 rows=1333333 width=12) (actual time=12.901..242.663 rows=79999 loops=1)
   Recheck Cond: (pk < 80000)
   Heap Blocks: exact=433
   Buffers: shared hit=654
   ->  Bitmap Index Scan on sandbox_pk_idx  (cost=0.00..24628.43 rows=1333333 width=0) (actual time=12.770..12.770 rows=79999 loops=1)
         Index Cond: (pk < 80000)
         Buffers: shared hit=221
 Planning time: 0.082 ms
 Execution time: 466.469 ms
Run Code Online (Sandbox Code Playgroud)

可以注意到: - 堆块要小得多:433,占总块的 2% 以上(21k) - 这些块被放入缓冲区,因为我们看到 hit

到目前为止我的理解是:

  • 块重新分区遵循 pk。所以第一个块包含从 1 到 200 的 pk,第二个包含从 201 到 400 的 pk,依此类推
  • 块重新分区与 完全正交x。在每个块中有一行x < 0.02
  • 因此,尽管有索引,但条件为 x 的查询将不得不读取所有块。由于 shared_buffer 可以处理更多块,因此每个块在每次查询时进出共享缓冲区。

这样对吗?

我有兴趣选择条件为 on 的行x并且仍然受益于共享缓冲区。这意味着我需要块以某种方式依赖于x. 我怎样才能做到这一点?

  • 我应该降低 block_size 吗?
  • 我应该使用分区吗?

其他想法?

小智 3

我同意@dezso,你应该开始分区,或者看看指令CLUSTERhttps://www.postgresql.org/docs/9.4/static/sql-cluster.html)。如上所述,它将根据索引信息对数据进行物理重新排序,这似乎是您想要实现的目标。