在 PostgreSQL 中壓縮序列
我
id serial PRIMARY KEY
在 PostgreSQL 表中有一個列。id
由於我刪除了相應的行,因此缺少許多s。現在我想通過重新啟動序列並以保留
id
原始順序的方式重新分配 s來“壓縮”表。id
可能嗎?例子:
- 現在:
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.
首先,序列中的間隙是可以預料的。問問自己是否真的需要刪除它們。如果你只是忍受它,你的生活會變得更簡單。為了獲得無間隙的數字,(通常更好的)替代方法是使用 a
VIEW
withrow_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;
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;
缺點如前所述。
- 臨時表,
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;
相關,更多細節: