2009-05-26 9 views
2

Sorry für eine ziemlich spezifische Frage.Löschen von Zeilen mit Spalte, die auf die gleiche Tabelle verweist dauert Bizzare Menge an Zeit

Ich habe eine Tabelle (siehe unten), und wenn ich versuche, eine Menge Datensätze daraus zu löschen, verbringt mein PostgreSQL 8.2.5 98% der Zeit mit der Eltern-Kind-Einschränkung. Ich versuche herauszufinden, welchen Index ich hinzufügen soll, damit es schnell geht. Ich muss sagen, dass alle Spalten in dieser Tabelle entweder 0 oder null als parent_block_id haben: es ist rudimentär.

Ich habe versucht, verschiedene Indizes hinzuzufügen: nur (parent_block_id); WHERE Elternblock = id = 0; WHERE parent_block_id ist NULL; WHERE parent_block_id! = 0. Keines dieser Elemente führte zu einem ernsthaften Leistungsvorteil.

varshavka=> explain analyze delete from infoblocks where template_id = 112; 
               QUERY PLAN 
------------------------------------------------------------------------------------------------------------- 
Seq Scan on infoblocks (cost=0.00..1234.29 rows=9 width=6) (actual time=13.271..40.888 rows=40000 loops=1) 
    Filter: (template_id = 112) 
Trigger for constraint $1: time=4051.219 calls=40000 
Trigger for constraint $2: time=1616.194 calls=40000 
Trigger for constraint cs_ibrs: time=2810.144 calls=40000 
Trigger for constraint cs_ibct: time=4026.305 calls=40000 
Trigger for constraint cs_ibbs: time=3517.640 calls=40000 
Trigger for constraint cs_ibreq: time=774344.010 calls=40000 
Total runtime: 790760.168 ms 
(9 rows) 



varshavka=> \d infoblocks 
             Table "public.infoblocks" 
    Column  |   Type    |      Modifiers 
-----------------+-----------------------------+------------------------------------------------------ 
id    | integer      | not null default nextval(('IB_SEQ'::text)::regclass) 
parent_block_id | integer      | 
nm_id   | integer      | default 0 
template_id  | integer      | not null 
author_id  | integer      | 
birthdate  | timestamp without time zone | not null 
Indexes: 
    "infoblocks_pkey" PRIMARY KEY, btree (id) 
    "zeroparent" btree (parent_block_id) WHERE parent_block_id <> 0 
Foreign-key constraints: 
    "$2" FOREIGN KEY (nm_id) REFERENCES newsmakers(nm_id) ON DELETE RESTRICT 
    "$5" FOREIGN KEY (author_id) REFERENCES users(user_id) ON DELETE RESTRICT 
    "cs_ibreq" FOREIGN KEY (parent_block_id) REFERENCES infoblocks(id) ON DELETE CASCADE 

Antwort

2

Vor allem: die erste (null!) Sache, die Sie tun sollten, wenn Sie hässliche Abfragezeiten bemerken, stellen Sie sicher, dass Sie VACUUM ANALYZE d vor kurzem haben.

Wenn Sie nur eine einmalige Löschung benötigen, dann sehen Sie araqnid's answer. Aber wenn Sie etwas benötigen, das auch in der Zukunft funktioniert, wenn einige Zeilen ein Feld haben, das nicht null ist, parent_block_id, lesen Sie weiter.

Ich vermute, dass PostgreSQL die Löschungen von ON DELETE CASCADE in einer einzigen Abfrage nicht kombiniert - die Tatsache, dass die Ausgabe EXPLAIN diese als Auslöser zeigt schlägt vor, dass jede Löschung der untergeordneten Zeile in der Tat separat durchgeführt wird. Vermutlich wird jede Zeile mit indexierter Suche auf parent_block_id gefunden werden, aber das wird immer noch viel langsamer als ein einzelner Durchlauf durch die Tabelle.

So könnten Sie wahrscheinlich eine große Beschleunigung erhalten, indem Sie die ON DELETE CASCADE zu ON DELETE RESTRICT ändern und manuell eine Liste aller Löschungen kompilieren, die in einer temporären Tabelle ausgeführt werden müssen, und dann alle auf einmal löschen. Dieser Ansatz ist sehr schnell, wenn die maximale Tiefe Ihrer Hierarchie klein ist. Hier einige Pseudo-Code:

# Insert the top-level rows as "seed" rows. 
INSERT INTO rows_to_delete 
    SELECT id, 0 FROM infoblocks WHERE template_id = 112 

# Gather all rows that are children of any row at depth curLevel, 
# advancing curLevel until no more children are found. 
curLevel = 0 
while (nRowsReturnedFromLastInsert > 0) { 
    INSERT INTO rows_to_delete 
     SELECT ib.id, rtd.level + 1 
     FROM infoblocks ib 
     JOIN rows_to_delete rtd ON (ib.parent_block_id = rtd.id) 
     WHERE rtd.level = curLevel 

    curLevel = curLevel + 1 
} 

DELETE FROM infoblocks 
    JOIN rows_to_delete rtd ON (infoblocks.id = rtd.id) 

(Ich bin nicht sicher, aber man kann in der Tat ON DELETE NO ACTION statt ON DELETE RESTRICT für die endgültige DELETE um erfolgreich zu sein verwenden müssen - es mir nicht klar ist, ob eine einzelne DELETE Aussage erlaubt, einen Elternteil und alle seine Nachkommen zu löschen, wenn ON DELETE RESTRICT in Kraft ist.Wenn das aus irgendeinem Grund nicht akzeptabel ist, könnten Sie immer mehrere DELETE Anweisungen durchlaufen, zuerst die unterste Ebene löschen, dann die nächste unterste usw.

+0

Danke für einen Hinweis, ich werde es morgen versuchen. Was VACUUM ANALYSE anbelangt, so habe ich das gemacht, es half in absoluten Zahlen um das Zehnfache, änderte aber nicht das zugrundeliegende O (N^2) ein bisschen. – alamar

+0

Sie hatten absolut Recht, ON DELETE RESTRICT magisch das Problem zerstört! – alamar

2

Haben Sie versucht, einen Index zu template_id hinzufügen?

+1

Das ist das eigentliche Problem, es macht einen sequentiellen Scan und nur einen von ihnen. –

+0

Setzen Sie einen Hash-Index darauf, wenn es sich um Ident-Felder handelt, die nicht sortiert oder für andere Zwecke als Suchen verwendet werden sollen, btree, wenn die Reihenfolge sinnvoll ist. –

+0

Das Problem ist nicht der Seq-Scan, das Problem ist, dass Trigger für Constraint cs_ibreq: Zeit = ** 774344.010 ** Aufrufe = 40000 – alamar

2

Wenn Sie alle anderen für eine Weile blockieren können, können Sie die Einschränkung cs_ibreq löschen, die Löschung durchführen und dann die Einschränkung erneut hinzufügen?

Möglicherweise, weil es nur einen Nicht-Null-Wert für parent_block_id gibt, verwendet es nicht den Index, wenn die Beschränkung geprüft wird? Obwohl das ein bisschen seltsam erscheint.

+0

+1 erlauben würden, aber beachten Sie, dass dies im allgemeinen Fall nicht funktionieren wird, wenn einige Datensätze ein nicht Null, nicht null Parent_block_id Feld haben. –

+0

+1 Dieser Tipp wird häufig in Oracle DBs verwendet. – ATorras

+0

Mit diesem Index ist das Ergebnis mehr oder weniger das gleiche. – alamar

Verwandte Themen