在PostgreSQL中压缩序列


9

id serial PRIMARY KEY在PostgreSQL表中有一列。id由于我已删除相应的行,因此缺少许多。

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

例:

  • 现在:

 id | data  
----+-------
  1 | hello
  2 | world
  4 | foo
  5 | bar
  • 后:

 id | data  
----+-------
  1 | hello
  2 | world
  3 | foo
  4 | bar

我尝试了StackOverflow答案中提出的建议,但没有成功:

# 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.

Answers:


9

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

这里有一些消除空白的方法。

1.全新的原始桌子

避免因独特的违规和餐桌膨胀而引起的并发症,而且速度很快。仅在简单情况下,您不受FK引用,表或其他相关对象的视图或并发访问的约束。这样做在一个事务中避免事故的发生:

BEGIN;
LOCK tbl;

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;

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

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

这样做的好处是,新表是无肿胀的,并且群集在上id

2. UPDATE就位

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

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

您可以将约束定义为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;

当您有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;

如上所述的缺点。

3. Temp表TRUNCATEINSERT

如果您有足够的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;

相关,有更多详细信息:


如果全部FOREIGN KEYS都设置为CASCADE不能,则不能简单地循环遍历旧的主键并就地更新它们的值(从旧值到新值)?本质上,这是选择三无TRUNCATE tbl,替换INSERTUPDATE,并且无需手动更新外键。
吉利

@吉利:可以,但是这种循环非常昂贵。由于索引中存在唯一的键冲突,因此无法一次更新整个表,因此UPDATE每行都需要一个单独的命令。请参见②中的解释或尝试自己看看。
Erwin Brandstetter

在我看来,性能并不是问题。从我的角度来看,有两种算法:“停止世界”的算法和无需关闭服务器即可在后台安静运行的算法。假设压缩仅在一次蓝月亮中发生一次(例如,当接近数据类型的上限时),那么实际上应该花费的时间并没有上限。只要压缩记录的速度快于添加新记录的速度,就可以了。
吉利
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.