暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

[译文] 迁移到PostgreSQL时表列类型和顺序的选择

原创 David Youatt 2021-08-03
338

PostgreSQL性能的一个被低估的因素可能是选择的数据类型及其在表中的组织。对于总是寻求增量性能改进的站点,管理行(也称为元组)的每个字节的确切布局和利用率可能是有用的。对于从其他数据库迁移到 PostgreSQL 的数据库来说,这是一个重要的考虑因素,因为PostgreSQL 中可用的数据类型及其布局方式与许多其他平台不同。

何时使用 NUMERIC/DECIMAL 数据类型 VS PostgreSQL 中的其他数值类型

重要的是,当试图挤出每一点性能时,优化数据的组织和最小化开销。其中大部分是由 PostgreSQL 程序员和编译器完成的,但您可以采取一些措施来提高性能,包括在表定义中选择正确的列类型和列顺序。

可变长度

NUMERIC 类型(与 DECIMAL 相同)对于货币之类的东西有意义,因为 PG 精确存储值,包括分数的小数位,并且可以精确表示的数字范围大于整数或大整数可以表示的范围,并支持存储数字的小数部分。计算是精确的,没有四舍五入,但存储格式是 base-10000,重要的是,它的存储是可变大小的。在包括 PostgreSQL 在内的大多数数据库中,有多种存储数字的选项的PostgreSQL文档为类型来表示数字的权威来源(或看看源代码)。

NUMERIC 的磁盘存储实际上是 base-10000,而不是 base-10。这意味着实际上每个以 10000 为基数的数字有 4 个以 10 为基数的数字,并且每个以 10000 为基数的数字占用 2 个字节。可变存储大小很重要的原因是可变长度数据添加了一个额外的头——当可变长度数据小于 127 字节时为 1 个字节,否则为 4 个字节——即使只是将两个数值相互比较也更多比对整数或大整数做同样的事情要昂贵。

那么,这在实践中是如何运作的呢?绝大多数数字的存储将小于 127 个字节,在这种情况下,您有:

  • 长度为 1 个字节(假设小于 127 个字节)
  • 数字标头的 2 个字节
  • 每个 base-10000 数字 2 个字节(最多 4 个 base-10 数字)

因此,数字往往需要 5 到 11 个字节来存储。

我们可以使用函数 pg_catalog.pg_column_size() 将其与 integer 和 bigint 所需的存储进行比较:

=> select c1 as numeric, pg_column_size(c1) as numeric_size, c2 as int, pg_column_size(c2) as int_size, c3 as bigint, pg_column_size(c3) as bigint_size from t1; numeric | numeric_size | int | int_size | bigint | bigint_size ------------------+--------------+------------+----------+------------------+------------- 1 | 5 | 1 | 4 | 1 | 8 12 | 5 | 12 | 4 | 12 | 8 123 | 5 | 123 | 4 | 123 | 8 1234 | 5 | 1234 | 4 | 1234 | 8 12345 | 7 | 12345 | 4 | 12345 | 8 123456 | 7 | 123456 | 4 | 123456 | 8 1234567 | 7 | 1234567 | 4 | 1234567 | 8 12345678 | 7 | 12345678 | 4 | 12345678 | 8 123456789 | 9 | 123456789 | 4 | 123456789 | 8 1234567890 | 9 | 1234567890 | 4 | 1234567890 | 8 12345678901 | 9 | | | 12345678901 | 8 123456789012 | 9 | | | 123456789012 | 8 1234567890123 | 11 | | | 1234567890123 | 8 12345678901234 | 11 | | | 12345678901234 | 8 123456789012345 | 11 | | | 123456789012345 | 8 1234567890123456 | 11 | | | 1234567890123456 | 8 (16 rows)

看看这个,我们可以看到“整数”总是比“数字”更小(更快!),一旦你达到数亿(并且它会当然也更快)。

固定长度的替代类型

不幸的是,人们 在从其他数据库迁移到 PostgreSQL 时经常使用 NUMERIC/DECIMAL类型,即使给定列中的实际值是整数(例如因为它是主键)。在这些情况下,通过使用 INTEGER 或 BIGINT,您将获得更好的性能和通常更少的空间。

PG 还支持 float 或 double 类型(REAL 和 DOUBLE PRECISION,你好 FORTRAN),如果不需要精确精度(例如测量),这可能是合适的。当然,PostgreSQL 支持 float(n) 的标准 SQL 类型语法,其中 n = 1…24 映射到 REAL,n = 25…53 映射到 DOUBLE PRECISION,而 float 表示 DOUBLE PRECISION。如果曾经不得不了解 IEEE 754 的详细信息,或者了解相关的指数和小数位,那么这些范围则看起来很熟悉。

与 NUMERIC 相比,使用固定宽度的数据类型可能会更有效,并且所需的空间可能更小(它确实取决于存储的确切值)。

字体大小、对齐方式和顺序

如果担心在磁盘上存储数据需要多少空间,那么列顺序也很重要。做二进制IO时,PG直接访问行中的二进制数据,不序列化数据。PostgreSQL 不会重新排序、跨列压缩(尽管给定列中的值可能会被压缩)或通常尝试避免浪费空间。这由数据库设计者来考虑和决定。

这种从磁盘到内存的直接映射的一个方面是必须遵守内存访问对齐,至少是为了性能,但有时也是为了功能,这取决于体系结构。这意味着数据类型必须存储在内存中的特定偏移量处,这可能会引入对齐“空洞”。为确保不会引入对齐孔,您应该首先使用最大的固定宽度列对表定义中的列进行排序,然后是较小的固定宽度列,最后是可变长度字段。

例如,如果有大小为 integer、bigint 的列,可能希望按以下顺序创建包含列的表:bigint、integer。

如果将其创建为整数 bigint,那么最终将在整数和 bigint 之间产生一个 4 字节的对齐孔(完全是死空间和浪费空间)。

例如:

0 4 8 12 16 20 24 28 32 +----+----+----+----+----+----+----+----+ |int4| W | bigint |int4| W | bigint | ... +----+----+----+----+----+----+----+----+

其中“W”是浪费空间,因为 bigint 将自然地对齐 8 字节地址。通过对列定义重新排序,您可以避免在内存和二进制存储中浪费空间:

0 4 8 12 16 20 24 28 32 +----+----+----+----+----+----+----+----+ | bigint | bigint |int4|int4| ... +----+----+----+----+----+----+----+----+

在这个简单的示例中,您已经节省了 8 字节的内存和存储空间,即 32 字节中的 8 字节或 25%。想象一下,如果您有包含许多列的宽行和包含许多行的大表。

PostgreSQL 将告知类型的大小以及它将如何与此查询对齐:

SELECT typname,typbyval,typlen,typalign FROM pg_catalog.pg_type ORDER BY 3 DESC,1;

这是前几行。请注意,-1 的长度是可变长度类型。请注意,一些更广泛的类型是几何类型,并注意 uuid 与两个 bigint 一样长,如果考虑使用 UUID,则应牢记这一点。

typname | typbyval | typlen | typalign ---------------------------------------+----------+--------+---------- name | f | 64 | c sql_identifier | f | 64 | c box | f | 32 | d lseg | f | 32 | d circle | f | 24 | d line | f | 24 | d interval | f | 16 | d point | f | 16 | d uuid | f | 16 | c aclitem | f | 12 | i timetz | f | 12 | d float8 | t | 8 | d int8 | t | 8 | d internal | t | 8 | d macaddr8 | f | 8 | i money | t | 8 | d

对于数字:

typname | typbyval | typlen | typalign ---------+----------+--------+---------- numeric | f | -1 | i (1 row)

为什么?

因为当 PostgreSQL 对存储进行二进制 IO 时,它使用内存中存储布局。它不会打包和拆包单个项目(列)以最小化尺寸。

但为什么?

CPU 希望数据与其自然地址位置“自然对齐”。例如,字节的自然对齐在 1 字节地址边界上,对于 2 字节地址上的短整数,4 字节地址上的 32 位整数(和浮点数),64 位整数(和双)在 8 字节边界上。像 ARM 和 MIPS 这样的 RISC CPU 严格执行这一点。如果数据不是自然对齐的,则会出现运行时错误。英特尔架构将在运行时调整未对齐的数据,但性能成本很高。幸运的是,这在编译 PostgreSQL 源代码时主要由编译器控制,尽管过于聪明的程序员可能会导致错位。

通常,PostgreSQL 源代码加上编译器会为您决定内存对齐方式,但它不会改变您定义事物的顺序。可以通过使用固定长度的类型(不是 DECIMAL 或 NUMERIC)来提供帮助,如果可以的话,通过从最大固定大小到最小固定大小的顺序声明表的列,然后是可变大小的数据,如 NUMERIC/DECIMAL。需要注意的是,像这样对列进行排序不仅会减少内存使用和存储需求,还会通过更好地使用硬件缓存和更好地使用 TLB 来影响性能,但这种做法已经被淘汰了。

请注意,标准的定长类型,如 int2、integer (int4)、bigint (int8)、REAL (float4)、DOUBLE PRECISION (float8) 将使用 CPU 本机类型和指令,而对多精度类型的操作将部分实现在软件。

概括

简而言之,可以通过以下方式帮助您的性能原因,存储方面、内存方面和 CPU 方面:

  • 在您真正需要时使用 NUMERIC/DECIMAL,例如数钱
  • 选择替代类型,如 INTEGER、BIGINT、REAL、DOUBLE PRECISION,当您不这样做时
  • 将表的列从最大的固定大小声明为最小的固定大小,然后是可变长度类型,例如 NUMERIC/DECIMAL

原文链接:https://blog.crunchydata.com/blog/choice-of-table-column-types-and-order-when-migrating-to-postgresql

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论