串行列在 PostgreSQL 中占用了不成比例的 space

Serial column takes up disproportional amount of space in PostgreSQL

我想在 PostgreSQL table 中创建一个 不是主键 的自动递增 id 列。 table 当前刚刚超过 200M 行,包含 14 列。

SELECT pg_size_pretty(pg_total_relation_size('mytable'));

以上查询显示 mytable 占用 57 GB 磁盘空间。在使用 df -h(在 Ubuntu 20.04)

检查后,我目前在磁盘上剩余 30 GB space

我不明白的是,为什么在尝试创建 SERIAL 列后,我完全 运行 磁盘 space - 查询最终永远不会完成。我运行以下命令:

ALTER TABLE mytable ADD COLUMN id SERIAL;

然后看看如何逐渐地,我的磁盘 space 运行 耗尽,直到什么都没有,查询失败。我不是数据库专家,但这没有意义。为什么一个简单的序列化列会占据 table 本身的 space 的一半以上,尤其是当它不是主键因此没有索引时?是否有创建此类自动递增 id 列的已知解决方法?

作为概念验证:

create table id_test(pk_fld integer primary key generated always as identity);
--FYI, in Postgres 14+ the overriding system value won't be needed.
--That is a hack around a bug in 13-
insert into id_test overriding system value values (default), (default);
select * from id_test;
 pk_fld 
--------
      1
      2
alter table id_test add column id_fld integer ;
update id_test set id_fld = 0;
alter table id_test alter COLUMN id_fld set not null;
alter table id_test alter COLUMN id_fld add generated always as identity;
update id_test set id_fld = default;
select * from id_test;
pk_fld | id_fld 
--------+--------
      1 |      1
      2 |      2

基本上,这会将过程分解为多个步骤。显然这只是一个玩具 table,并不代表您的设置。我会在测试 table 上尝试它,它是您实际 table 的一个子集,以查看磁盘 space 消耗会发生什么。在更新数据库的 return 行后使用 VACUUM 不会有什么坏处。

添加 serial 列就是添加具有非常量 DEFAULT 值的 integer 列。这将导致 PostgreSQL 重写 table,因为必须将新列值添加到所有现有行。所以 PostgreSQL 写了一份 table 的新副本,并在完成后丢弃旧的。这将暂时需要比原始 table 多一倍的磁盘 space,这就解释了为什么 运行 磁盘 space.

不足

您可以将操作分成几个步骤:

ALTER TABLE mytable ADD id bigint;
CREATE SEQUENCE mytable_id_seq OWNED BY mytable.id;
ALTER TABLE mytable ALTER id SET DEFAULT nextval('mytable_id_seq');

这不会重写 table,并且会保持现有行不变。这些列的 id 的值将为 NULL。

您可能希望将现有行更新为 NOT NULL,但要小心:如果您一次更新它们,您也会 运行 磁盘不足 space ,因为在 PostgreSQL 中 UPDATE 将行的完整新版本写入 table。您必须分批更新行,并且 运行 VACUUM 在这些更新之间。

总而言之,这相当烦人和复杂。所以帮自己一个忙,增加磁盘space。这是最简单和最好的解决方案。