Najbardziej efektywny sposób zbiorczego usuwania wierszy z postgresu

23

Zastanawiam się, jaki byłby najbardziej efektywny sposób usuwania dużej liczby wierszy z PostgreSQL, ten proces byłby codziennie częścią powtarzającego się zadania masowego importowania danych (delta wstawiania + usuwania) do tabeli. Mogą być tysiące, potencjalnie miliony wierszy do usunięcia.

Mam plik kluczy podstawowych, po jednym w wierszu. Dwie opcje, o których myślałem, były zgodne z poniższymi, ale nie znam / nie rozumiem wystarczająco dużo elementów wewnętrznych PostgreSQL, aby podjąć świadomą decyzję, która byłaby najlepsza.

  • Wykonaj DELETEzapytanie dla każdego wiersza w pliku, używając prostego WHEREklucza podstawowego (lub zgrupuj usuwane partie nza pomocą IN()klauzuli)
  • Zaimportuj klucze podstawowe do tabeli tymczasowej za pomocą COPYpolecenia, a następnie usuń je z tabeli głównej za pomocą łączenia

Wszelkie sugestie będą mile widziane!

Tarnfeld
źródło
1
To samo pytanie zostało udzielone bardziej szczegółowo tutaj: stackoverflow.com/a/8290958
Simon

Odpowiedzi:

25

Druga opcja jest znacznie czystsza i będzie działać wystarczająco dobrze, aby było warto. Alternatywą jest budowanie gigantycznych zapytań, których planowanie i wykonywanie będzie dość uciążliwe. Zasadniczo lepiej będzie, jeśli PostgreSQL wykona pracę tutaj. Ogólnie rzecz biorąc, znalazłem aktualizacje dziesiątek tysięcy wierszy w sposób, który opisujesz, aby zapewnić odpowiednią wydajność, ale jest jedna ważna rzecz, której należy unikać.

Aby to zrobić, użyj opcji select i join w usuniętym pliku.

DELETE FROM foo WHERE id IN (select id from rows_to_delete);

W żadnym wypadku nie powinieneś robić z dużym stołem:

DELETE FROM foo WHERE id NOT IN (select id from rows_to_keep);

To zwykle powoduje, że antijoin z zagnieżdżoną pętlą powoduje, że wydajność jest raczej problematyczna. Jeśli w końcu będziesz musiał wybrać tę trasę, zrób to zamiast tego:

DELETE FROM foo 
WHERE id IN (select id from foo f 
          LEFT JOIN rows_to_keep d on f.id = d.id
              WHERE d.id IS NULL);

PostgreSQL jest zwykle całkiem dobry w unikaniu złych planów, ale wciąż istnieją przypadki związane z zewnętrznymi złączeniami, które mogą mieć duży wpływ na dobre i złe plany.

Wędruje to nieco dalej, ale wydaje mi się, że warto o tym wspomnieć, ponieważ łatwo jest przejść z IN do NOT IN i oglądać zbiornik wydajności zapytania.

Chris Travers
źródło
To bardzo pomogło, dzięki! Odkryłem jednak, że użycie „łączenia zapytań” jest bardziej wydajne w tym konkretnym przypadku. Np. IN ( select id from foo except select id from rows_to_keep ) Zobacz postgresql.org/docs/9.4/static/queries-union.html
Ufos
1

Natknąłem się na to pytanie, ponieważ miałem podobny problem. Czyszczę bazę danych, która ma ponad 300 milionów wierszy, ostateczna baza danych będzie miała tylko około 30% oryginalnych danych. Jeśli masz do czynienia z podobnym scenariuszem, w rzeczywistości łatwiej jest wstawić do nowej tabeli i ponownie zaindeksować zamiast usuwać.

Zrób coś takiego

CREATE temp_foo as SELECT * FROM foo WHERE 1=2;
INSERT INTO temp_foo (SELECT * FROM foo where foo.id IN (SELECT bar.id FROM BAR);

Dzięki odpowiedniemu indeksowaniu na foo i pasku możesz uniknąć skanowania Seq.

Następnie musiałbyś ponownie zaindeksować i zmienić nazwę tabeli.

Niro
źródło