估计Postgres索引的大小

问题描述

我试图更好地理解创建Postgres索引时所涉及的权衡。作为其中的一部分,我很想了解通常使用多少空间索引。我已阅读the docs,但找不到任何相关信息。我一直在做自己的小实验来创建表和索引,但是如果有人能够解释大小的原因,那将是惊人的。假设有一个具有100万行的公用表,其中每一行都有一个唯一的id一个唯一的outstanding

CREATE TABLE account (
    id integer,active boolean NOT NULL,outstanding double precision NOT NULL,);

以及创建的索引

  • CREATE INDEX id_idx ON account(id)
  • CREATE INDEX outstanding_idx ON account(outstanding)
  • CREATE INDEX id_outstanding_idx ON account(id,outstanding)
  • CREATE INDEX active_idx ON account(active)
  • CREATE INDEX partial_id_idx ON account(id) WHERE active

您如何估计索引大小(以字节为单位)?更重要的是,为什么?

解决方法

您可以自己计算。每个索引条目的开销为8个字节。添加索引数据的平均大小(内部二进制格式)。

还有更多的开销,例如页眉和页脚以及内部索引页,但这并不能算多,除非您的索引行很宽。

,

由于您未指定index type,因此,我假设使用默认的 B树索引。其他类型可能会大不相同。

这是一种简单函数,用于计算具有给定列的给定表上索引的估计的最小大小(以字节为单位)

CREATE OR REPLACE FUNCTION f_index_minimum_size(_tbl regclass,_cols VARIADIC text[],OUT estimated_minimum_size bigint)
  LANGUAGE plpgsql AS
$func$
DECLARE
   _missing_column text;
BEGIN

-- assert
SELECT i.attname
FROM   unnest(_cols) AS i(attname)
LEFT   JOIN pg_catalog.pg_attribute a ON a.attname = i.attname
                                     AND a.attrelid = _tbl
WHERE  a.attname IS NULL
INTO   _missing_column;

IF FOUND THEN
   RAISE EXCEPTION 'Table % has no column named %',_tbl,quote_ident(_missing_column);
END IF;


SELECT INTO estimated_minimum_size
       COALESCE(1 + ceil(reltuples/trunc((blocksize-page_overhead)/(4+tuple_size)))::int,0) * blocksize -- AS estimated_minimum_size
FROM  (
   SELECT maxalign,blocksize,reltuples,fillfactor,page_overhead,(maxalign  -- up to 16 columns,else nullbitmap may force another maxalign step
         + CASE WHEN datawidth <= maxalign  THEN maxalign
                WHEN datawidth%maxalign = 0 THEN datawidth
                ELSE                            (datawidth + maxalign) - datawidth%maxalign END  -- add padding to the data to align on MAXALIGN
          ) AS tuple_size
   FROM  (
      SELECT c.reltuples,count(*),90 AS fillfactor,current_setting('block_size')::bigint AS blocksize,CASE WHEN version() ~ '64-bit|x86_64|ppc64|ia64|amd64|mingw32'  -- MAXALIGN: 4 on 32bits,8 on 64bits
                  THEN 8 ELSE 4 END AS maxalign,40 AS page_overhead  -- 24 bytes page header + 16 bytes "special space"
           -- avg data width without null values,sum(ceil((1-COALESCE(s.null_frac,0)) * COALESCE(s.avg_width,1024))::int) AS datawidth  -- ceil() because avg width has a low bias
      FROM   pg_catalog.pg_class     c
      JOIN   pg_catalog.pg_attribute a ON a.attrelid = c.oid
      JOIN   pg_catalog.pg_stats     s ON s.schemaname = c.relnamespace::regnamespace::text
                                      AND s.tablename  = c.relname
                                      AND s.attname    = a.attname
      WHERE  c.oid = _tbl
      AND    a.attname = ANY(_cols) --  all exist,verified above
      GROUP  BY 1
      ) sub1
   ) sub2;
END
$func$;

通话示例:

SELECT f_index_minimum_size('my_table','col1','col2','col3');

SELECT f_index_minimum_size('public.my_table',VARIADIC '{col1,col2,col3}');

db 提琴here

关于VARIADIC参数:

基本上 ,所有索引通常使用块大小为8 kb(很少为4 kb)的数据页。首先, B树索引需要一个 个数据页。每个附加数据页的固定开销为40个字节(当前)。每个页面都存储元组,如手册here中所述。每个元组都有一个元组标头(通常为8个字节,包括对齐方式填充),一个空位图,一个数据(可能包括多列索引的列之间的对齐方式填充)以及对齐方式填充到MAXALIGN的下一个倍数(通常为8个字节)。另外,每个元组有一个4字节的ItemId。最初可能会保留一些空间以供以后添加,其中fillfactor-B树索引默认为90%。

重要注释和免责声明

报告的大小是估计的最小大小。由于分页的自然膨胀,实际索引通常会增加25%左右。另外,该计算不会考虑多列之间可能的对齐填充。可以再增加百分之几(在极端情况下则可以增加更多)。参见:

估计基于视图pg_stats中的列统计信息,该视图基于系统表pg_statistics。 (直接使用后者会更快,但只允许超级用户使用。)尤其是,计算基于null_frac,即“空列条目的分数”和avg_width,即“平均值”。列条目的字节宽度”以计算平均数据宽度-忽略了多列索引可能使用的其他对齐填充。

已考虑默认的90%fillfactor。 (一个可以指定另一个。)

B树索引通常自然会出现高达50%的膨胀,这没什么好担心的。

不适用于表达式索引。

不提供部分索引。

如果传递了除现有普通列名称之外的任何内容,函数将引发异常。区分大小写!

如果表是新表(或者在任何情况下,如果统计信息可能已过期),请确保在表上运行 ANALYZE ,然后再调用函数进行更新(甚至启动!)统计信息。

由于进行了重大优化, Postgres 12 中的B树索引占用的空间更少,通常更接近于报告的最小大小。

不说明 Postgres 13 引入的deduplication,后者可以压缩具有重复值的索引。

部分代码摘自ioguix的膨胀估算查询:

有关Postgres源代码的更多详细信息,