我有一张像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)
);
以上每行最多可添加50个字节。我的经验是,我需要另外40%到50%的系统开销,甚至没有任何用户创建的索引。所以,每行约75个字节。我将在表中有许多行,可能超过1450亿行,因此该表将推动13-14太字节。我可以使用什么技巧来压缩这个表?我可能的想法如下......
将real
值转换为integer
。如果它们可以存储为smallint
,那么每个字段可节省2个字节。
将列b .. m转换为数组。我不需要搜索这些列,但我确实需要能够一次返回一列的值。所以,如果我需要列g,我可以做类似
的事情SELECT a, arr[5] FROM t;
我可以使用数组选项节省空间吗?会有速度惩罚吗?
还有其他想法吗?
答案 0 :(得分:167)
实际上,您可以执行 某些内容 ,但这需要更深入的了解。关键字为 对齐填充 。 Every data type has specific alignment requirements
您可以通过优先排序来最小化填充 列之间 的空间。以下(极端)示例将浪费大量物理磁盘空间:
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)
要保存每行 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
根据经验,如果先放入8字节列,那么4字节,2字节和1字节列最后就不会出错。
boolean
,uuid
和其他一些类型不需要对齐填充。 text
,varchar
和其他“varlena”(可变长度)类型名义上需要“int”对齐(大多数机器上为4个字节)。但事实上,磁盘格式中没有对齐填充(与RAM不同)。我在许多测试中验证过。最后,我在note in the source code:
另请注意,我们允许在存储“打包”varlenas时违反名义对齐;
通常情况下,每行最多可以节省几个字节“column tetris”。在大多数情况下,这些都不是必需的。但是数十亿行可以轻松实现几千兆字节。
您可以使用函数pg_column_size()
测试实际的列/行大小
某些类型在RAM中占用的空间比在磁盘上占用的空间大(压缩或“打包”格式)。当使用pg_column_size()
测试相同的值(或值行与表行)时,您可以获得更大的常量(RAM格式)结果,而不是表格列。
最后,某些类型可能是compressed or "toasted"(存储在行外)或两者都有。
项目指针每行4个字节 - 不受上述考虑 并且至少有24个字节(23 +填充)用于元组头。 The manual on Database Page Layout:
有一个固定大小的标题(在大多数机器上占用23个字节), 后跟可选的空位图,可选的对象ID字段和 用户数据。
对于标头和用户数据之间的填充,您需要知道服务器上的MAXALIGN
- 通常是64位操作系统上的8个字节(或32位操作系统上的4个字节)。如果您不确定,请查看pg_controldata
。
在 Postgres二进制目录中运行以下内容以获得明确答案:
./pg_controldata /path/to/my/dbcluster
实际用户数据(行的列)从偏移量开始 由
t_hoff
表示,其必须始终是MAXALIGN
的倍数 平台的距离。
因此,您通常可以通过将数据打包为8个字节的倍数来获得最佳存储空间。
您发布的示例中没有任何内容可以获得。它已经紧紧包装好了。最后int2
后的2个字节填充,最后4个字节。您可以在最后将填充合并到6个字节,这不会改变任何内容。
数据页面大小通常为8 KB。这个级别也有一些开销/膨胀:余数不足以容纳另一个元组,更重要的是死行或FILLFACTOR
setting保留的百分比。
要考虑磁盘大小还有其他几个因素:
对于您正在评估的数组类型,您将为该类型添加 24字节的开销。此外,阵列元素像往常一样占据空间。什么都没有。
答案 1 :(得分:10)
在数组中存储多个数字字段时,我看不到任何好处(以及丢失的东西)。
size of each numerical type已清楚记录,您应该使用与您所需的范围分辨率兼容的最小尺寸类型;这就是你所能做的一切。
我不认为(但我不确定)是否对行中的列有一些字节对齐要求,在这种情况下,列的重新排序可能会改变使用的空间 - 但我不认为如此。
顺便说一下,每行都有一个修复开销,约为23 bytes。
答案 2 :(得分:2)
摘自这份出色的文档: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
答案 3 :(得分:-2)
在这样的工作流中使用 No-SQL 数据库,因为它们像 json 一样,没有像行那样的东西,你宁愿将每个键的数据存储在那里,每个键的结构都是相互独立的。
一些例子是 MongoDB、Cassandra。