在PostgreSQL中计算和节省空间

pun*_*ish 58 postgresql storage database-design bigdata

我有一个像pg这样的表:

CREATE TABLE t (
    a BIGSERIAL NOT NULL,               -- 8 b
    b SMALLINT,                         -- 2 b
    c SMALLINT,                         -- 2 b
    d REAL,                             -- 4 b
    e REAL,                             -- 4 b
    f REAL,                             -- 4 b
    g INTEGER,                          -- 4 b
    h REAL,                             -- 4 b
    i REAL,                             -- 4 b
    j SMALLINT,                         -- 2 b
    k INTEGER,                          -- 4 b
    l INTEGER,                          -- 4 b
    m REAL,                             -- 4 b
    CONSTRAINT a_pkey PRIMARY KEY (a)
);
Run Code Online (Sandbox Code Playgroud)

以上每行最多可添加50个字节.我的经验是,我需要另外40%到50%的系统开销,甚至没有任何用户创建的索引.所以,每行约75个字节.我将在表中有许多行,可能超过1450亿行,因此该表将推动13-14太字节.我可以使用什么技巧来压缩这个表?我的可能想法如下......

real值转换为integer.如果它们可以存储为smallint,则每个字段节省2个字节.

将列b .. m转换为数组.我不需要搜索这些列,但我确实需要能够一次返回一列的值.所以,如果我需要列g,我可以做类似的事情

SELECT a, arr[5] FROM t;
Run Code Online (Sandbox Code Playgroud)

我会用数组选项节省空间吗?会有速度惩罚吗?

还有其他想法吗?

Erw*_*ter 177

"列俄罗斯方块"

实际上,你可以做点什么,但这需要更深入的了解.关键字是对齐填充.每种数据类型都有特定的对齐要求.

您可以通过有利地排序来最小化列之间填充的空间损失.以下(极端)示例将浪费大量物理磁盘空间:

CREATE TABLE t (
    e int2    -- 6 bytes of padding after int2
  , a int8
  , f int2    -- 6 bytes of padding after int2
  , b int8
  , g int2    -- 6 bytes of padding after int2
  , c int8
  , h int2    -- 6 bytes of padding after int2
  , d int8)
Run Code Online (Sandbox Code Playgroud)

要保存每行24个字节,请改用:

CREATE TABLE t (
    a int8
  , b int8
  , c int8
  , d int8
  , e int2
  , f int2
  , g int2
  , h int2)   -- 4 int2 occupy 8 byte (MAXALIGN), no padding at the end
Run Code Online (Sandbox Code Playgroud)

SQL小提琴.

根据经验,如果先放入8字节列,那么4字节,2字节和1字节列最后就不会出错.

boolean,uuid以及其他一些类型不需要对齐填充.text,varchar等"varlena"(可变长度)类型名义上需要"INT"对齐(在大多数机器4个字节).但事实上,磁盘格式中没有对齐填充(与RAM不同).我在许多测试中验证过.最后,我在源代码注释中找到了解释:

还要注意,当存储"打包"varlenas时,我们允许违反名义对齐;

通常情况下,每行最多可以节省几个字节,最好玩"列俄罗斯方块".在大多数情况下,这些都不是必需的.但是数十亿行可以轻松实现几千兆字节.

您可以使用该函数测试实际的列/行大小pg_column_size().
某些类型在RAM中占用的空间比在磁盘上占用的空间大(压缩或"打包"格式).当测试相同的值(或值行与表行)时,您可以获得更大的常量(RAM格式)结果,而不是表格列pg_column_size().

最后,某些类型可以被压缩或"烘烤"(存储在线外)或两者.

每个元组的开销(行)

项目指针每行4个字节 - 不受上述考虑.
并且至少有24个字节(23 +填充)用于元组头.数据库页面布局手册:

有一个固定大小的头(在大多数机器上占用23个字节),后跟一个可选的空位图,一个可选的对象ID字段和用户数据.

对于标头和用户数据之间的填充,您需要知道MAXALIGN您的服务器 - 通常是64位操作系统上的8个字节(或32位操作系统上的4个字节).如果您不确定,请查看pg_controldata.

Postgres二进制目录中运行以下命令以获得明确的答案:

./pg_controldata /path/to/my/dbcluster
Run Code Online (Sandbox Code Playgroud)

手册:

实际用户数据(行的列)从指示的偏移开始,该偏移t_hoff必须始终是MAXALIGN 平台距离的倍数.

因此,您通常通过将数据打包为8个字节的倍数来获得最佳存储空间.

您发布示例中没有任何好处.它已经紧紧包装好了.最后一个填充后的2个字节,最后的int24个字节.您可以在最后将填充合并到6个字节,这不会改变任何内容.

每个数据页面的开销

数据页大小通常为8 KB.在这个级别也有一些开销/膨胀:余数不足以容纳另一个元组,更重要的是死行或FILLFACTOR设置保留的百分比.

磁盘大小还有几个其他因素需要考虑:

数组类型?

对于您正在评估的数组,您将为数组类型添加24字节的开销.此外,阵列的元素像往常一样占据空间.没有什么可以获得的.

  • @Russ:因为没有人实现逻辑和物理列顺序之间的划分.[这是一个开放的TODO项目](https://wiki.postgresql.org/index.php?title=Alter_column_position&action=history),但不是微不足道的,因为它在整个地方搞乱了系统目录.大约6年后仍然如此.从Postgres 9.3开始,`VIEW`可以用于为*simple*case提供不同的列顺序(写入也会自动传播.) (5认同)
  • 这是一个很好/有趣的答案,但有一点我不明白为什么`CREATE TABLE`中列名的顺序很重要.我认为这不重要.为什么不/不能postgres为你做这个俄罗斯方块优化?为什么列顺序被认为足以保持定义? (4认同)
  • *根据经验,如果你先放8字节列,那么4字节,2字节和1字节列最后你就不会出错.*一些合法的酷建议. (3认同)
  • 谢谢,@ Erwin.但是,留给用户似乎仍然很奇怪.在创建表格之后,我会弄乱系统目录,但如果重新排序是在前面完成的话,它似乎不会是一个问题.实现这一点的存储参数之类的问题是什么?"CREATE TABLE WITH column_reorder_ok"中的某些内容意味着"我不关心逻辑列顺序,所以要调整它,但是你喜欢优化表".或者,用你的术语"WITH column_tetris_ok".:) (3认同)
  • @Russ:听起来像一个有用的功能.在创建表之前重新排序列*的工具,这避免了混乱系统目录的复杂性.也可以在任何客户端软件中实现,这是一种优化`CREATE TABLE`语句以实现最小存储的工具...... (2认同)

leo*_*loy 11

在数组中存储多个数字字段时,我看不到任何可以获得的东西(以及丢失的东西).

每种数字类型大小都清楚地记录下来,您应该使用与您所需的范围分辨率兼容的最小尺寸类型; 这就是你所能做的一切.

我不认为(但我不确定)是否对行中的列有一些字节对齐要求,在这种情况下,列的重新排序可能会改变所使用的空间 - 但我不这么认为.

顺便说一句,每行有一个修复开销,大约23个字节.

  • 从9.2开始,行标题每行24个字节,页面偏移量为4个字节(存储在页眉中),或每行28个字节.还有其他项可以参与播放,例如每8列支持NULL值1个字节(NULL值存储为位掩码). (3认同)
  • @Sean:这不太正确.根据手册[这里](http://www.postgresql.org/docs/current/interactive/storage-page-layout.html),行标题(HeapTupleHeader)有23个字节,而不是24个字节:`有一个固定大小的头(在大多数机器上占用23个字节),后跟可选的空位图,可选的对象ID字段.差异是相关的,具有多达8列的表的NULL位掩码适合这一个备用字节,使得这些表有效地释放空存储空间. (3认同)
  • 然而,正确地,由于数据类型的对齐,在字节23和24之间几乎肯定存在漏洞,并且从字节25开始INT开始.所以标头只有23个字节,但消耗的空间是24个字节. (2认同)

jbo*_*xxx 6

从这个伟大的文档:https : //www.2ndquadrant.com/en/blog/on-rocks-and-sand/

对于您已经拥有的表,或者您正在开发的表,命名为my_table,此查询将给出从左到右的最佳顺序。

SELECT a.attname, t.typname, t.typalign, t.typlen
FROM pg_class c
JOIN pg_attribute a ON (a.attrelid = c.oid)
JOIN pg_type t ON (t.oid = a.atttypid)
WHERE c.relname = 'my_table'
 AND a.attnum >= 0
ORDER BY t.typlen DESC
Run Code Online (Sandbox Code Playgroud)


Але*_*щук 6

这是关于 Erwin 的列重新排序建议的一个很酷的工具: https: //github.com/NikolayS/postgres_dba

它有确切的命令——p1:

在此输入图像描述

然后它会自动向您显示所有表上的列重新排序的真正潜力:

在此输入图像描述