【问题标题】:Estimating size of Postgres indexes估计 Postgres 索引的大小
【发布时间】:2020-12-24 21:05:22
【问题描述】:

我试图更好地了解创建 Postgres 索引所涉及的权衡。作为其中的一部分,我很想了解通常使用多少空间索引。我已经阅读了the docs,但找不到任何相关信息。我一直在做自己的小实验来创建表和索引,但如果有人能解释为什么大小是这样的,那就太棒了。假设像这样一个有 1M 行的公共表,其中每一行都有一个唯一的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

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

【问题讨论】:

  • 更多列通常意味着较大的索引大小。请注意,在您的最后一个(第五个)示例中,WHERE active 并不意味着更大的尺寸,因为active 实际上并不是一个键。相反,它可能意味着一个更小的索引,因为由于 where 子句,更少的 id 值必须是索引。

标签: postgresql indexing storage


【解决方案1】:

由于您没有指定index type,我将假设默认的B-tree 索引。其他类型可能有很大不同。

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

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-tree 索引 一开始就有一个 数据页开销。每个额外的数据页都有 40 字节的固定开销(当前)。每个页面都存储像手册here 中描述的元组。每个元组都有一个元组标头(通常为 8 个字节,包括对齐填充)、可能是空位图、数据(可能包括多列索引的列之间的对齐填充),以及可能与 MAXALIGN 的下一个倍数的对齐填充(通常为 8字节)。另外,每个元组有一个ItemId 4 个字节。最初可能会保留一些空间供以后添加使用 fillfactor - 默认情况下为 B-tree 索引 90 %。

重要说明和免责声明

报告的尺寸是估计的最小尺寸。由于页面拆分导致的自然膨胀,实际索引通常会大 25% 左右。此外,该计算并未考虑多列之间可能的对齐填充。可以再增加几个百分点(在极端情况下甚至更多)。见:

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

默认 90 % fillfactor 被考虑在内。 (可以指定不同的。)

对于 B-tree 索引而言,通常会出现高达 50% 的膨胀,无需担心。

不适用于表达式索引。

不提供部分索引。

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

如果表是新表(或者在任何情况下统计数据可能已过时),请务必在调用函数更新(甚至启动! ) 统计数据。

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

不考虑 Postgres 13 引入的 deduplication,它可以压缩具有重复值的索引。

部分代码取自 ioguix 的膨胀估计查询:

更多详细信息请参见此处的 Postgres 源代码:

【讨论】:

    【解决方案2】:

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

    还有一些额外的开销,例如页眉和页脚以及内部索引页,但这并不占太多,除非您的索引行非常宽。

    【讨论】:

    • 好的,不管数据类型如何,它都是 8 个字节?这在索引多列时如何工作,是否会为CREATE INDEX id_outstanding_idx ON account(id, outstanding) 每行添加 16 个字节?
    • 抱歉误导。如果索引中有 bigintsmallint,则索引条目的大小将为 8 + 8 + 2。
    猜你喜欢
    • 2023-03-21
    • 2010-09-09
    • 1970-01-01
    • 1970-01-01
    • 2010-09-15
    • 2013-08-21
    • 2020-02-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多