suj*_*eet 5 postgresql index primary-key amazon-rds
我需要向一个高流量的大型 PostgreSQL 表(大约 2TB)添加主键。这是一项关键操作,我正在寻找如何有效地完成该操作的指导。
我已经尝试过以下步骤:
-- Step 1: Add id identity column
ALTER TABLE users
ADD COLUMN id BIGINT GENERATED ALWAYS as IDENTITY;
-- Step 2: Add unique index on (id, user_id) concurrently
CREATE UNIQUE INDEX CONCURRENTLY id_user_id_idx
ON users (id, user_id);
-- verify that step 2 is completed
-- Step 3: Add primary key
ALTER TABLE users
ADD CONSTRAINT users_pkey PRIMARY KEY USING INDEX id_user_id_idx;
Run Code Online (Sandbox Code Playgroud)
我面临两个问题:
表完全锁定在“步骤 1”本身上。
我知道这是预料之中的,但如果有任何选择可以避免这种情况,请提出建议。
我收到以下错误,
错误:无法扩展文件“base/16401/90996”:设备上没有剩余空间提示:检查可用磁盘空间。
但600GB
我的服务器上还有剩余的存储空间。
由于表将被锁定在“第 1 步”,并且如果没有选项可以避免这种情况,我可以利用停机时间id
先添加列,然后运行其他两个脚本。
我不知道这是否可以解决存储错误。
请提供任何建议,以便我能够以尽可能少的停机时间添加 PK。
PostgreSQL v14.6
Erw*_*ter 12
您的步骤 1 需要的空间远远超过 600 GB(暂时)。该表大约有 2 TB。至少必须有尽可能多的bigint
可用空间(减去可能的膨胀,再加上每行新列 8 个字节),因为该更改迫使 Postgres 重写整个表。
相反,请按以下顺序执行:
添加一个没有默认值的可为空的列id
,因此它最初是这样的null
。
ALTER TABLE users ADD COLUMN id bigint;
Run Code Online (Sandbox Code Playgroud)
这样,Postgres 就可以应付微小的元数据更改。没有表重写,没有阻塞。
我会将 PK 列命名为“user_id”,而不喜欢广泛使用、非描述性且高度重复的名称“id”。但保留“id”以与问题保持一致。
手动创建SEQUENCE
:
CREATE SEQUENCE users_id_seq;
Run Code Online (Sandbox Code Playgroud)
使列“拥有”序列:
ALTER SEQUENCE users_id_seq OWNED BY users.id;
Run Code Online (Sandbox Code Playgroud)
添加列默认值,该列仅在新行中生效。
ALTER TABLE users ALTER COLUMN id SET DEFAULT nextval('users_id_seq');
Run Code Online (Sandbox Code Playgroud)
看:
null
以总大小(或其他大小)的 1% 左右的批量更新预先存在的行(仍包含值)。在单独的事务中,允许自动清理启动并标记死行以供重用。这样,表就不会增长太多,600 GB 就足够了。
自从Postgres 11中添加了SQL程序,我们就可以COMMIT
在匿名代码块中。假设有一个timestamptz
列users.inserted_at
(最好有一个索引!),这样的东西可以工作:
DO
$do$
DECLARE
_ts timestamptz := (SELECT COALESCE(min(inserted_at), now()) FROM users); -- must not be NULL
_step interval := '7 days'; -- adjust to your data !!!
BEGIN
LOOP
RAISE NOTICE 'Updating rows starting from %', _ts; -- optional
UPDATE users
SET id = nextval('users_id_seq')
WHERE inserted_at >= _ts
AND inserted_at < _ts + _step
AND id IS NULL; -- optional
EXIT WHEN NOT FOUND AND _ts >= now(); -- adjust to your case !!!
COMMIT; -- Requires Postgres 11+ !!!
PERFORM pg_sleep(10); -- adapt to your setup: long enough so let autovacuum kick in
_ts := _ts + _step;
END LOOP;
END
$do$;
Run Code Online (Sandbox Code Playgroud)
或者,在客户端中循环,并VACUUM users;
在迭代之间运行以确保空间得到重用。(VACUUM
不能在事务内运行。)
看:
最终,所有旧行都被更新。
现在创建唯一索引CONCURRENTLY
,以避免阻塞插入。与您的步骤 2 类似,但仅限于(id)
:
CREATE UNIQUE INDEX CONCURRENTLY users_id_idx ON users (id);
Run Code Online (Sandbox Code Playgroud)
我看不出加入user_id
PK 的充分理由。如果您需要它进行仅索引扫描,请考虑使用INCLUDE (user_id)
. 但这并不总是有益的。看:
现在使用唯一索引添加新的 PK,而不阻止插入(您的步骤 3):
ALTER TABLE users ADD CONSTRAINT users_pkey PRIMARY KEY USING INDEX users_id_idx;
Run Code Online (Sandbox Code Playgroud)
这也将隐式设置列NOT NULL
。
最后,使用Peter Eisentraut 的函数 upgrade_serial_to_identity(tbl regclass, col name)
将 转换serial
为IDENTITY
列。作为超级用户:
SELECT upgrade_serial_to_identity('users'::regclass, 'id')
Run Code Online (Sandbox Code Playgroud)
或者坚持serial
PK,也许就足够了。
有关的: