TOAST
TOAST 是 PostgreSQL 用于防止物理数据行大小超过数据块大小(通常为 8KB)的一种机制。Postgres 不支持跨块边界物理行,因此块大小是行大小的硬性上限。为了允许用户表拥有比这更宽的行,TOAST 机制将宽字段值分解成更小的部分,这些部分存储在与用户表关联的 TOAST 表中“行外”。
您创建的每个表都有自己关联的(唯一的)TOAST 表,该表可能永远不会被使用,也可能被使用,这取决于您插入的行的大小。所有这些对用户都是透明的,并且默认情况下是启用的。
当要存储的行“太宽”时(默认情况下该阈值为 2KB),TOAST 机制首先尝试压缩任何宽字段值。如果这不足以使行小于 2KB,它会将宽字段值分解成块,这些块将存储在关联的 TOAST 表中。每个原始字段值将被一个小的指针替换,该指针显示在 TOAST 表中查找此“行外”数据的位置。TOAST 会尝试以这种方式将用户表行压缩到 2KB,但只要它能降到 8KB 以下,就足够了,并且行可以成功存储。
所有标准的 Postgres 数据类型,其值可能大于 2KB,都支持以这种方式被“TOAST”处理,大多数潜在的宽扩展数据类型也是如此。
您可以通过打开 psql 并运行以下命令来查看表的当前 TOAST 选项
\d+ table_name
例如
=> \d+ test_table_name Table "name_space.test_table_name" Column | Type | Modifiers | Storage | Stats target | Description --------+-------------------+-----------+----------+--------------+------------- foo | character(100000) | | extended | |
您可以像这样修改存储类型
alter table test_blob alter column column_name set storage EXTENDED;
默认的 TOAST 选项是 EXTENDED,它告诉系统首先尝试压缩该列的数据。如果这还不够,它会将数据“行外”存储到 TOAST 表中。如果您不想使用压缩或行外存储,可以有其他选项可供选择。请注意完全禁用行外存储,因为这会导致尝试存储过大的行完全失败。(请注意,非 TOASTable 数据类型的列将显示存储选项为“plain”,并且您无法更改它。)
总表大小限制
这里需要注意的是,对于每个被移到“行外”的值,都会生成一个 OID 并用于在该表的关联 TOAST 表中跟踪它。您在一个表中不能有超过 2^32(40 亿)个行外值,因为它的 TOAST 表中必须有重复的 OID。实际上,您希望每个表中的 TOASTed 值数量远小于此数量,因为随着 OID 空间的填充,系统可能会花费大量时间搜索下一个空闲 OID,因为它需要生成一个新的行外值。
用于此目的的 OID 是从一个全局计数器生成的,该计数器每 40 亿个值就会环绕一次,因此有时会再次生成一个已使用的值。Postgres 会检测到这种情况,并尝试使用下一个 OID。因此,只有在特定的 TOAST 表中存在很长一段时间的已使用 OID 值,并且没有间隙的情况下,才会发生非常缓慢的插入。这不太可能发生,部分原因是使用全局计数器往往会将连续的 OID 分散到不同的表中。但这可能会发生。
请注意,重要的是表中宽值的总数。如果您有许多宽列,那么您可以在表中拥有的行数可能远小于 40 亿。对您的表进行分区是一个可能的解决方法。
还值得强调的是,只有大于 2KB 的字段值才会以这种方式消耗 TOAST OID。因此,实际上,要达到这个限制,需要在一个表中拥有许多 TB 的数据,尤其是在您拥有各种各样的值宽度的情况下。
列数限制
TOAST 并没有完全消除物理块大小对行宽度的限制,因为它通过缩小单个字段来工作,并且它不能使这些字段小于指针(18 字节)。因此,即使是完全的 toasting 也不允许一个行拥有超过大约 450 个宽列。此外,固定宽度字段类型(如整数)不支持被 toasting,因此拥有大量这些类型的列也会导致行宽度限制问题。
如果您不介意使用定制版本的 Postgres,一个可能的解决方法是使用更大的块大小。但这只能起到有限的作用。通常,如果您开始遇到此限制,您应该重新设计您的表模式。例如,考虑将相同数据类型的列组合成数组。