Wie lösche ich eine feste Anzahl von Zeilen mit Sortierung in PostgreSQL?

107

Ich versuche, einige alte MySQL-Abfragen auf PostgreSQL zu portieren, aber ich habe Probleme mit dieser:

DELETE FROM logtable ORDER BY timestamp LIMIT 10;

PostgreSQL erlaubt keine Reihenfolge oder Einschränkungen in seiner Löschsyntax, und die Tabelle hat keinen Primärschlüssel, sodass ich keine Unterabfrage verwenden kann. Außerdem möchte ich das Verhalten beibehalten, bei dem die Abfrage genau die angegebene Anzahl oder Datensätze löscht. Wenn die Tabelle beispielsweise 30 Zeilen enthält, aber alle denselben Zeitstempel haben, möchte ich trotzdem 10 löschen, obwohl dies keine Rolle spielt welche 10.

So; Wie lösche ich eine feste Anzahl von Zeilen mit Sortierung in PostgreSQL?

Bearbeiten: Kein Primärschlüssel bedeutet, dass keine log_idSpalte oder ähnliches vorhanden ist. Ah, die Freuden älterer Systeme!

Was ist es
quelle
1
Warum nicht den Primärschlüssel hinzufügen? Stück Kuchen in postgresql : alter table foo add column id serial primary key.
Wayne Conrad
Das war mein ursprünglicher Ansatz, aber andere Anforderungen verhindern ihn.
Whatsit

Antworten:

159

Sie könnten versuchen, Folgendes zu verwenden ctid:

DELETE FROM logtable
WHERE ctid IN (
    SELECT ctid
    FROM logtable
    ORDER BY timestamp
    LIMIT 10
)

Das ctidist:

Der physische Speicherort der Zeilenversion in ihrer Tabelle. Beachten Sie, dass ctiddie Zeile zwar sehr schnell gefunden werden kann, sich ctidjedoch ändert, wenn sie aktualisiert oder verschoben wird VACUUM FULL. Daher ctidist es als langfristige Zeilenkennung unbrauchbar.

Es gibt oidaber auch nur das, wenn Sie beim Erstellen der Tabelle ausdrücklich danach fragen.

mu ist zu kurz
quelle
Das funktioniert, aber wie zuverlässig ist es? Gibt es Fallstricke, auf die ich achten muss? Ist es möglich, dass VACUUM FULLAutovakuum Probleme verursacht, wenn sie die ctidWerte in der Tabelle ändern, während die Abfrage ausgeführt wird?
Whatsit
2
Inkrementelle VAKUUMEN ändern die CTIDs nicht, glaube ich nicht. Da dies nur innerhalb jeder Seite komprimiert wird und die ctid nur die Zeilennummer ist, kein Seitenversatz. Eine VACUUM FULL- oder eine CLUSTER-Operation würde die ctid ändern, aber diese Operationen erhalten zuerst eine exklusive Zugriffssperre für die Tabelle.
Araqnid
@Whatsit: Mein Eindruck von der ctidDokumentation ist, dass sie ctidstabil genug ist, damit dieses LÖSCHEN funktioniert, aber nicht stabil genug, um beispielsweise als Ghetto-FK in eine andere Tabelle aufgenommen zu werden. Vermutlich aktualisieren Sie das nicht, logtablesodass Sie sich keine Gedanken über diese Änderungen machen müssen, ctidund VACUUM FULLsperren die Tabelle ( postgresql.org/docs/current/static/routine-vacuuming.html ), sodass Sie sich keine Sorgen machen müssen der andere Weg, den ctids ändern kann. @ araqnids PostgreSQL-Fu ist ziemlich stark und die Dokumente stimmen ihm zu, um zu booten.
Mu ist zu kurz
Vielen Dank an Sie beide für die Klarstellung. Ich habe mir die Dokumente angesehen, war mir aber nicht sicher, ob ich sie richtig interpretierte. Ich war vorher noch nie auf Ctids gestoßen.
Whatsit
Dies ist eigentlich eine ziemlich schlechte Lösung, da Postgres den TID-Scan in Joins nicht verwenden kann (IN ist ein besonderer Fall davon). Wenn Sie sich den Plan ansehen, sollte er ziemlich schrecklich sein. "Sehr schnell" gilt also nur, wenn Sie CTID explizit angeben. Das Gesagte ist ab Version 10.
Greatvovan
53

Postgres-Dokumente empfehlen, Array anstelle von IN und Unterabfrage zu verwenden. Dies sollte viel schneller funktionieren

DELETE FROM logtable 
WHERE id = any (array(SELECT id FROM logtable ORDER BY timestamp LIMIT 10));

Diesen und einige andere Tricks finden Sie hier

Kritik
quelle
@Konrad Garus Hier geht es zum Link 'Schnelle erste n Zeilen entfernen'
Kritik
1
@BlakeRegalia Nein, da die angegebene Tabelle keinen Primärschlüssel enthält. Dadurch werden alle Zeilen mit einer "ID" gelöscht, die in den ersten 10 gefunden wurde. Wenn alle Zeilen dieselbe ID haben, werden alle Zeilen gelöscht.
Philip Whitehouse
6
Wenn any (array( ... ));es schneller ist als das in ( ... ), klingt es wie ein Fehler im Abfrageoptimierer - es sollte in der Lage sein, diese Transformation zu erkennen und dasselbe mit den Daten selbst zu tun.
rjmunro
1
Ich fand diese Methode erheblich langsamer als die Verwendung INauf einer UPDATE(was der Unterschied sein könnte).
Jmervine
1
Messung an einer 12-GB-Tabelle: erste Abfrage 450..1000 ms, zweite 5..7 Sekunden: Schnelle Abfrage: Löschen aus cs_logging, wobei id = any (Array (ID aus cs_logging auswählen, wobei date_created <now () - Intervall '1 Tage '* 30 und partition_key wie'% I 'nach ID-Limit 500 sortieren)) Langsame: Löschen aus cs_logging wo id in (ID aus cs_logging auswählen wo date_created <now () - Intervall' 1 Tage '* 30 und partition_key wie'% Ich bestelle nach ID-Limit 500). Die Verwendung von ctid war viel langsamer (Minuten).
Guido Leenders
14
delete from logtable where log_id in (
    select log_id from logtable order by timestamp limit 10);
Konrad Garus
quelle
2

Angenommen, Sie möchten 10 Datensätze (ohne Bestellung) löschen, können Sie dies tun:

DELETE FROM logtable as t1 WHERE t1.ctid < (select t2.ctid from logtable as t2  where (Select count(*) from logtable t3  where t3.ctid < t2.ctid ) = 10 LIMIT 1);

Für meinen Anwendungsfall, 10 Millionen Datensätze zu löschen, stellte sich heraus, dass dies schneller war.

Patrick Hüsler
quelle
1

Sie können eine Prozedur schreiben, die das Löschen für einzelne Zeilen durchläuft. Die Prozedur kann einen Parameter verwenden, um die Anzahl der Elemente anzugeben, die Sie löschen möchten. Aber das ist ein bisschen übertrieben im Vergleich zu MySQL.

Bernhard
quelle
0

Wenn Sie keinen Primärschlüssel haben, können Sie die Syntax Array Where IN mit einem zusammengesetzten Schlüssel verwenden.

delete from table1 where (schema,id,lac,cid) in (select schema,id,lac,cid from table1 where lac = 0 limit 1000);

Das hat bei mir funktioniert.

user2449151
quelle