PostgreSQL索引缓存


16

我很难找到有关如何在PostgreSQL中缓存索引的“一般性”解释,因此我希望对以下任何或所有假设进行现实检查:

  1. PostgreSQL索引(如行)位于磁盘上,但可以缓存。
  2. 索引可能完全在高速缓存中,或者根本不存在。
  3. 是否缓存它取决于使用频率(由查询计划者定义)。
  4. 因此,大多数“明智的”索引将一直存在于缓存中。
  5. 索引buffer cache与行位于同一高速缓存(?)中,因此索引所使用的高速缓存空间不可用于行。


我理解这一点的动机来自另一个问题,我曾问过有人建议在不能访问大多数数据的表上使用部分索引

在进行此操作之前,我想弄清楚使用部分索引有两个优点:

  1. 我们减小了缓存中索引的大小,从而为缓存中的行本身释放了更多空间。
  2. 我们减小了B树的大小,从而加快了查询响应速度。

4
使用部分索引不仅在很少访问大量数据时有用,而且在某些值非常普遍时也很有用。当值很常见时,计划者将仍然使用表扫描代替索引,因此将值包含在索引中没有任何作用。
Eelke 2012年

Answers:


19

pg_buffercache,我可以得到一些问题的答案。

  1. 这是很明显的,但(5)的结果也表明答案为
  2. 我尚未为此建立一个很好的例子,现在它比是更多:)(请参阅下面的编辑,答案为“ 否”)。)
  3. 由于计划者是决定是否使用索引的人,我们可以说,它决定缓存(但这要复杂得多)
  4. 缓存的确切细节可以从源代码中获得,除了这个主题之外,我在这个主题上找不到太多的东西(也请参见作者的答案)。但是,我很确定这比简单的是或否要复杂得多。(再次,从我的编辑中您可以得到一些想法-由于缓存大小有限,因此这些“明智的”索引争夺可用空间。如果它们太多,它们将互相从缓存中踢出-因此答案是“ 否”。 )
  5. 作为带有show的简单查询pg_buffercache,答案是肯定的YES。值得注意的是,临时表数据不会在此处缓存。

编辑

我发现Jeremiah Peschka 关于表和索引存储的精彩文章。有了那里的信息,我也可以回答(2)。我设置了一个小测试,所以您可以自己检查这些。

-- we will need two extensions
CREATE EXTENSION pg_buffercache;
CREATE EXTENSION pageinspect;


-- a very simple test table
CREATE TABLE index_cache_test (
      id serial
    , blah text
);


-- I am a bit megalomaniac here, but I will use this for other purposes as well
INSERT INTO index_cache_test
SELECT i, i::text || 'a'
FROM generate_series(1, 1000000) a(i);


-- let's create the index to be cached
CREATE INDEX idx_cache_test ON index_cache_test (id);


-- now we can have a look at what is cached
SELECT c.relname,count(*) AS buffers
FROM 
    pg_class c 
    INNER JOIN pg_buffercache b ON b.relfilenode = c.relfilenode 
    INNER JOIN pg_database d ON (b.reldatabase = d.oid AND d.datname = current_database())
GROUP BY c.relname
ORDER BY 2 DESC LIMIT 10;

             relname              | buffers
----------------------------------+---------
 index_cache_test                 |    2747
 pg_statistic_relid_att_inh_index |       4
 pg_operator_oprname_l_r_n_index  |       4
... (others are all pg_something, which are not interesting now)

-- this shows that the whole table is cached and our index is not in use yet

-- now we can check which row is where in our index
-- in the ctid column, the first number shows the page, so 
-- all rows starting with the same number are stored in the same page
SELECT * FROM bt_page_items('idx_cache_test', 1);

 itemoffset |  ctid   | itemlen | nulls | vars |          data
------------+---------+---------+-------+------+-------------------------
          1 | (1,164) |      16 | f     | f    | 6f 01 00 00 00 00 00 00
          2 | (0,1)   |      16 | f     | f    | 01 00 00 00 00 00 00 00
          3 | (0,2)   |      16 | f     | f    | 02 00 00 00 00 00 00 00
          4 | (0,3)   |      16 | f     | f    | 03 00 00 00 00 00 00 00
          5 | (0,4)   |      16 | f     | f    | 04 00 00 00 00 00 00 00
          6 | (0,5)   |      16 | f     | f    | 05 00 00 00 00 00 00 00
...
         64 | (0,63)  |      16 | f     | f    | 3f 00 00 00 00 00 00 00
         65 | (0,64)  |      16 | f     | f    | 40 00 00 00 00 00 00 00

-- with the information obtained, we can write a query which is supposed to
-- touch only a single page of the index
EXPLAIN (ANALYZE, BUFFERS) 
    SELECT id 
    FROM index_cache_test 
    WHERE id BETWEEN 10 AND 20 ORDER BY id
;

 Index Scan using idx_test_cache on index_cache_test  (cost=0.00..8.54 rows=9 width=4) (actual time=0.031..0.042 rows=11 loops=1)
   Index Cond: ((id >= 10) AND (id <= 20))
   Buffers: shared hit=4
 Total runtime: 0.094 ms
(4 rows)

-- let's have a look at the cache again (the query remains the same as above)
             relname              | buffers
----------------------------------+---------
 index_cache_test                 |    2747
 idx_test_cache                   |       4
...

-- and compare it to a bigger index scan:
EXPLAIN (ANALYZE, BUFFERS) 
SELECT id 
    FROM index_cache_test 
    WHERE id <= 20000 ORDER BY id
;


 Index Scan using idx_test_cache on index_cache_test  (cost=0.00..666.43 rows=19490 width=4) (actual time=0.072..19.921 rows=20000 loops=1)
   Index Cond: (id <= 20000)
   Buffers: shared hit=4 read=162
 Total runtime: 24.967 ms
(4 rows)

-- this already shows that something was in the cache and further pages were read from disk
-- but to be sure, a final glance at cache contents:

             relname              | buffers
----------------------------------+---------
 index_cache_test                 |    2691
 idx_test_cache                   |      58

-- note that some of the table pages are disappeared
-- but, more importantly, a bigger part of our index is now cached

总而言之,这表明索引和表可以通过页面缓存的页面,因此答案(2)NO

最后一个示例说明此处未缓存临时表:

CREATE TEMPORARY TABLE tmp_cache_test AS 
SELECT * FROM index_cache_test ORDER BY id FETCH FIRST 20000 ROWS ONLY;

EXPLAIN (ANALYZE, BUFFERS) SELECT id FROM tmp_cache_test ORDER BY id;

-- checking the buffer cache now shows no sign of the temp table

1
+1非常好的答案。有道理,没有缓存RAM中的临时表。不过,我想知道,是否临时表一旦溢出到磁盘(由于缺少足够的缓存)就发生缓存temp_buffers-是针对整个表还是只是磁盘上的一部分。我希望后者。可能会是一个有趣的测试..
Erwin Brandstetter

9

当查询确定索引页将有助于减少回答查询所需的表数据量时,将提取索引页。仅索引索引的块可以完成读取。是的,它们进入存储表数据的同一个shared_buffers池中。两者也都由操作系统缓存作为第二层缓存来支持。

您可以轻松地在内存中拥有索引的0.1%或100%。当您的查询仅涉及表的一个子集时,大多数“'明智'的索引将一直存在于缓存中”的想法很难解决。一个常见的例子是您是否有时间导向的数据。通常,这些人通常会浏览表格的最新内容,很少浏览旧历史。在那里,您可能会找到导航到内存中最近端和附近所需的所有索引块,而浏览早期记录所需的索引块却很少。

实现的复杂部分不是块如何进入缓冲区缓存。这是关于他们何时离开的规则。我在PostgreSQL缓冲区高速缓存的内部讨论以及其中包含的示例查询可以帮助您了解那里发生的事情,并查看生产服务器上真正积累的内容。可能令人惊讶。在PostgreSQL 9.0 High Performance中,所有这些主题都有很多本书中,内容也很多。

部分索引可能会有所帮助,因为它们会减小索引的大小,因此既可以更快地导航,又可以留出更多RAM来缓存其他内容。如果索引的导航方式使您触摸的部分始终位于RAM中,则可能并不能带来真正的改进。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.