在 PostgreSQL 中压缩序列

rub*_*bik 9 postgresql primary-key sequence gaps-and-islands

id serial PRIMARY KEY在 PostgreSQL 表中有一个列。id因为我删除了相应的行,所以缺少许多s。

现在我想通过重新启动序列并以保留id原始id顺序的方式重新分配s来“压缩”表。是否可以?

例子:

  • 现在:

 id | data  
----+-------
  1 | hello
  2 | world
  4 | foo
  5 | bar
Run Code Online (Sandbox Code Playgroud)
  • 后:

 id | data  
----+-------
  1 | hello
  2 | world
  3 | foo
  4 | bar
Run Code Online (Sandbox Code Playgroud)

我尝试了 StackOverflow answer 中建议的内容,但没有奏效:

# alter sequence t_id_seq restart;
ALTER SEQUENCE
# update t set id=default;
ERROR:  duplicate key value violates unique constraint t_pkey
DETAIL:  Key (id)=(1) already exists.
Run Code Online (Sandbox Code Playgroud)

Erw*_*ter 9

首先,序列中的间隙是可以预料的。问问自己是否真的需要删除它们。如果你只是忍受它,你的生活就会变得更简单。要获得无间隙数字,(通常更好)的替代方法是使用VIEWwith row_number()。此相关答案中的示例:

这里有一些消除间隙的方法。

1. 新的、原始的桌子

避免了独特的违规和表膨胀带来的并发症,并且速度很快。仅适用于不受 FK 引用、表或其他依赖对象上的视图或并发访问约束的简单情况。在一笔交易中做到,以避免发生意外:

BEGIN;
LOCK tbl; -- optionally: IN SHARE MODE to allow concurrent reads

CREATE TABLE tbl_new (LIKE tbl INCLUDING ALL);

INSERT INTO tbl_new -- no target list in this case
SELECT row_number() OVER (ORDER BY id), data  -- all columns in default order
FROM   tbl;

ALTER SEQUENCE tbl_id_seq OWNED BY tbl_new.id;  -- make new table own sequence

DROP TABLE tbl;
ALTER TABLE tbl_new RENAME TO tbl;

SELECT setval('tbl_id_seq', max(id)) FROM tbl;  -- reset sequence

COMMIT;
Run Code Online (Sandbox Code Playgroud)

CREATE TABLE tbl_new (LIKE tbl INCLUDING ALL)复制结构,包括。原始表中的约束和默认值。然后使新表列拥有序列:

并将其重置为新的最大值:

这带来了新表不会膨胀并且聚集在 上的优点id

2.UPDATE到位

这会产生很多死行,并且VACUUM稍后需要(自动)。

如果该serial也是PRIMARY KEY(如您的情况)或具有UNIQUE约束,则您必须避免流程中的唯一违规。PK / UNIQUE 约束的(更便宜的)默认值是NOT DEFERRABLE,这会强制在每一行之后进行检查。此相关问题下的所有详细信息:

您可以将约束定义为DEFERRABLE(这使得它更昂贵)。
或者,您可以删除约束并在完成后重新添加:

BEGIN;

LOCK tbl;

ALTER TABLE tbl DROP CONSTRAINT tbl_pkey;  -- remove PK

UPDATE tbl t  -- intermediate unique violations are ignored now
SET    id = t1.new_id
FROM  (SELECT id, row_number() OVER (ORDER BY id) AS new_id FROM tbl) t1
WHERE  t.id = t1.id;

SELECT setval('tbl_id_seq', max(id)) FROM tbl;  -- reset sequence

ALTER TABLE tbl ADD CONSTRAINT tbl_pkey PRIMARY KEY(id); -- add PK back

COMMIT;
Run Code Online (Sandbox Code Playgroud)

当您有FOREIGN KEY引用列的约束时,两者都不可能,因为(每个文档):

被引用的列必须是被引用表中不可延迟的唯一或主键约束的列。

您需要(锁定所有涉及的表并)删除/重新创建 FK 约束并手动更新所有 FK 值(请参阅选项3。)。或者你必须在一秒钟内将值移开UPDATE以避免冲突。例如,假设您没有负数:

BEGIN;
LOCK tbl;

UPDATE tbl SET id = id * -1;  -- avoid conflicts

UPDATE tbl t
SET    id = t1.new_id
FROM  (SELECT id, row_number() OVER (ORDER BY id DESC) AS new_id FROM tbl) t1
WHERE  t.id = t1.id;

SELECT setval('tbl_id_seq', max(id)) FROM tbl;  -- reset sequence

COMMIT;
Run Code Online (Sandbox Code Playgroud)

缺点如上所述。

3. 临时表TRUNCATE,,INSERT

如果您有足够的 RAM,还有一种选择。这结合了前两种方式的一些优点。几乎和选项1一样快,你会得到一个原始的新表,没有膨胀,但像选项2一样保留所有约束和依赖项
但是根据文档:

TRUNCATE 不能用于具有 来自其他表的外键引用的表,除非所有这些表也在同一命令中被截断。在这种情况下检查有效性将需要表扫描,而重点不是做一个。

大胆强调我的。

您可以暂时删除 FK 约束并使用数据修改 CTE 来更新所有 FK 列:

SET temp_buffers = 500MB;   -- example value, see 1st link below

BEGIN;

CREATE TEMP TABLE tbl_tmp AS
SELECT row_number() OVER (ORDER BY id) AS new_id, *
FROM   tbl
ORDER  BY id;  -- order here to use index (if one exists)

-- drop FK constraints in other tables referencing this one
-- which takes out an exclusive lock on those tables

TRUNCATE tbl;

INSERT INTO tbl
SELECT new_id, data  -- list all columns in order
FROM tbl_tmp;        -- rely on established order in tbl_tmp
-- ORDER BY id;      -- only to be absolutely sure (not necessary)

--  example for table "fk_tbl" with FK column "fk_id"
UPDATE fk_tbl f
SET    fk_id = t.new_id  -- set to new ID
FROM   tbl_tmp t
WHERE  f.fk_id = t.id;   -- match on old ID

-- add FK constraints in other tables back

COMMIT;
Run Code Online (Sandbox Code Playgroud)

相关,有更多细节: