Komprimieren einer Sequenz in PostgreSQL


9

Ich habe eine id serial PRIMARY KEYSpalte in einer PostgreSQL-Tabelle. Viele ids fehlen, weil ich die entsprechende Zeile gelöscht habe.

Jetzt möchte ich die Tabelle "komprimieren", indem ich die Sequenz neu starte und das ids so neu zuweise , dass die ursprüngliche idReihenfolge erhalten bleibt. Ist es möglich?

Beispiel:

  • Jetzt:

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

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

Ich habe versucht, was in einer StackOverflow-Antwort vorgeschlagen wurde , aber es hat nicht funktioniert:

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

Antworten:


9

Zunächst sind Lücken in einer Sequenz zu erwarten. Fragen Sie sich, ob Sie sie wirklich entfernen müssen. Ihr Leben wird einfacher, wenn Sie nur damit leben. Um lückenlose Zahlen zu erhalten, ist die (oft bessere) Alternative die Verwendung eines VIEWwith row_number(). Beispiel in dieser verwandten Antwort:

Hier sind einige Rezepte, um Lücken zu schließen.

1. Neuer, makelloser Tisch

Vermeidet Komplikationen mit einzigartigen Verstößen und Aufblähen und ist schnell . Nur für einfache Fälle, in denen Sie nicht an FK-Referenzen, Ansichten in der Tabelle oder andere abhängige Objekte oder an gleichzeitigen Zugriff gebunden sind. Machen Sie es in einer Transaktion, um Unfälle zu vermeiden:

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)kopiert die Struktur inkl. Einschränkungen und Standardeinstellungen aus der ursprünglichen Tabelle. Machen Sie dann die neue Tabellenspalte zu einer eigenen Sequenz:

Und setzen Sie es auf das neue Maximum zurück:

Dies hat den Vorteil, dass die neue Tabelle frei von Blähungen und Clustern ist id.

2. UPDATEan Ort und Stelle

Dies erzeugt viele tote Zeilen und erfordert (automatisch) VACUUMspäter.

Wenn die serialSpalte auch die ist PRIMARY KEY(wie in Ihrem Fall) oder eine UNIQUEEinschränkung aufweist, müssen Sie dabei eindeutige Verstöße vermeiden . Die (billigere) Standardeinstellung für PK / UNIQUE-Einschränkungen lautet: Dies NOT DEFERRABLEerzwingt eine Überprüfung nach jeder einzelnen Zeile. Alle Details unter dieser verwandten Frage zu SO:

Sie können Ihre Einschränkung als definieren DEFERRABLE(was sie teurer macht).
Oder Sie können die Einschränkung löschen und wieder hinzufügen, wenn Sie fertig sind:

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;

Beides ist nicht möglich, wenn SieFOREIGN KEYEinschränkungenhaben,die auf die Spalte (n) verweisen, weil ( gemäß Dokumentation ):

Die referenzierten Spalten müssen die Spalten einer nicht aufschiebbaren eindeutigen oder Primärschlüsseleinschränkung in der referenzierten Tabelle sein.

Sie müssten (alle beteiligten Tabellen sperren und) FK-Einschränkungen löschen / neu erstellen und alle FK-Werte manuell aktualisieren (siehe Option 3 ). Oder Sie müssen Werte innerhalb einer Sekunde aus dem Weg räumen UPDATE, um Konflikte zu vermeiden. Angenommen, Sie haben keine negativen Zahlen:

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;

Nachteile wie oben erwähnt.

3. Temp Tabelle TRUNCATE,INSERT

Eine weitere Option, wenn Sie viel RAM haben. Dies kombiniert einige der Vorteile der ersten beiden Möglichkeiten. Fast so schnell wie Option 1. und Sie eine unberührten bekommen, neue Tabelle ohne aufblasen , aber halten alle Zwänge und Abhängigkeiten in Ort wie in Option 2
jedoch , pro Dokumentation:

TRUNCATE kann nicht für eine Tabelle verwendet werden, die Fremdschlüsselreferenzen aus anderen Tabellen enthält, es sei denn, alle diese Tabellen werden im selben Befehl ebenfalls abgeschnitten. Die Überprüfung der Gültigkeit in solchen Fällen würde Tabellenscans erfordern, und der springende Punkt ist, keine durchzuführen.

Meine kühne Betonung.

Sie können FK-Einschränkungen vorübergehend löschen und datenmodifizierende CTEs verwenden, um alle FK-Spalten zu aktualisieren:

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;

Verwandte, mit mehr Details:


Wenn alle auf eingestellt FOREIGN KEYSsind, CASCADEkönnen Sie nicht einfach die alten Primärschlüssel durchlaufen und ihre Werte direkt aktualisieren (vom alten auf den neuen Wert)? Im Wesentlichen ist dies Option 3 ohne TRUNCATE tbl, durch INSERTein zu ersetzen UPDATEund ohne dass Fremdschlüssel manuell aktualisiert werden müssen.
Gili

@Gili: Sie könnten , aber diese Art von Schleife ist extrem teuer. Da Sie aufgrund eindeutiger Schlüsselverletzungen im Index nicht die gesamte Tabelle auf einmal aktualisieren können, benötigen Sie UPDATEfür jede Zeile einen separaten Befehl. Siehe Erklärung in ② oder versuchen Sie es selbst.
Erwin Brandstetter

Ich denke nicht, dass Leistung in meinem Fall ein Problem ist. So wie ich das sehe, gibt es zwei Arten von Algorithmen: solche, die "die Welt aufhalten" und solche, die leise im Hintergrund laufen, ohne den Server herunterfahren zu müssen. Unter der Annahme, dass die Verdichtung in einem blauen Mond nur einmal auftritt (z. B. wenn Sie sich der Obergrenze eines Datentyps nähern), gibt es keine Obergrenze für die Zeit, die benötigt wird. Solange wir Datensätze schneller komprimieren als neue hinzugefügt werden, sollte es uns gut gehen.
Gili
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.