PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Jak wykonać duże, nieblokujące aktualizacje w PostgreSQL?

Kolumna / Wiersz

... Nie potrzebuję zachowania integralności transakcyjnej podczas całej operacji, ponieważ wiem, że kolumna, którą zmieniam, nie będzie zapisywana ani odczytywana podczas aktualizacji.

Dowolna UPDATE w modelu MVCC PostgreSQL pisze nową wersję całego wiersza . Jeśli równoczesne transakcje zmienią się dowolne kolumny tego samego wiersza, pojawiają się czasochłonne problemy ze współbieżnością. Szczegóły w instrukcji. Znajomość tej samej kolumny nie zostaną dotknięte równoczesnymi transakcjami unika niektórych możliwe komplikacje, ale nie inne.

Indeks

Aby uniknąć przekierowania do dyskusji nie na temat, załóżmy, że wszystkie wartości statusu dla 35 milionów kolumn są obecnie ustawione na tę samą (nie pustą) wartość, co czyni indeks bezużytecznym.

Podczas aktualizowania całej tabeli (lub jego główne części) Postgres nigdy nie używa indeksu . Skanowanie sekwencyjne jest szybsze, gdy wszystkie lub większość wierszy musi zostać odczytana. Wręcz przeciwnie:utrzymanie indeksu oznacza dodatkowy koszt UPDATE .

Wydajność

Załóżmy na przykład, że mam tabelę o nazwie „zamówienia” z 35 milionami wierszy i chcę to zrobić:

UPDATE orders SET status = null;

Rozumiem, że dążysz do bardziej ogólnego rozwiązania (patrz poniżej). Ale aby odpowiedzieć na rzeczywiste pytanie pytanie:można to zrobić w sprawie milisekund , niezależnie od wielkości stołu:

ALTER TABLE orders DROP column status
                 , ADD  column status text;

Instrukcja (do Postgres 10):

Gdy kolumna jest dodawana za pomocą ADD COLUMN , wszystkie istniejące wiersze w tabeli są inicjowane domyślną wartością kolumny (NULL jeśli nie DEFAULT klauzula jest określona). Jeśli nie ma DEFAULT klauzula, to jest tylko zmiana metadanych [...]

Instrukcja (od Postgres 11):

Gdy kolumna jest dodawana za pomocą ADD COLUMN i nieulotny DEFAULT jest określony, wartość domyślna jest oceniana w momencie wyrażenia, a wynik jest przechowywany w metadanych tabeli. Ta wartość zostanie użyta w kolumnie we wszystkich istniejących wierszach. Jeśli nie DEFAULT jest określony, używana jest wartość NULL. W żadnym przypadku nie jest wymagane przepisanie tabeli.

Dodanie kolumny z nietrwałym DEFAULT lub zmiana typu istniejącej kolumny będzie wymagała przepisania całej tabeli i jej indeksów. [...]

Oraz:

DROP COLUMN formularz nie usuwa fizycznie kolumny, ale po prostu czyni ją niewidoczną dla operacji SQL. Kolejne operacje wstawiania i aktualizowania w tabeli będą przechowywać wartość null dla kolumny. Dzięki temu usunięcie kolumny jest szybkie, ale nie spowoduje natychmiastowego zmniejszenia rozmiaru tabeli na dysku, ponieważ miejsce zajmowane przez upuszczoną kolumnę nie jest odzyskiwane. Miejsce zostanie odzyskane z czasem, gdy istniejące wiersze zostaną zaktualizowane.

Upewnij się, że nie masz obiektów zależnych od kolumny (ograniczenia klucza obcego, indeksy, widoki, ...). Będziesz musiał je upuścić / odtworzyć. Poza tym małe operacje na tabeli katalogu systemowego pg_attribute wykonać pracę. Wymaga wyłącznej blokady na stole, co może stanowić problem przy dużym obciążeniu współbieżnym. (Jak podkreśla Buurman w swoim komentarzu.) Poza tym operacja to kwestia milisekund.

Jeśli masz domyślną kolumnę, którą chcesz zachować, dodaj ją ponownie w osobnym poleceniu . Wykonanie tego w tym samym poleceniu powoduje natychmiastowe zastosowanie go do wszystkich wierszy. Zobacz:

  • Dodać nową kolumnę bez blokady tabeli?

Aby faktycznie zastosować wartość domyślną, rozważ zrobienie tego w partiach:

  • Czy PostgreSQL optymalizuje dodawanie kolumn z wartościami domyślnymi innymi niż NULL?

Rozwiązanie ogólne

dblink została wspomniana w innej odpowiedzi. Umożliwia dostęp do „zdalnych” baz danych Postgres w niejawnych oddzielnych połączeniach. „Zdalna” baza danych może być aktualna, dzięki czemu można osiągnąć „transakcje autonomiczne” :to, co funkcja zapisuje w "zdalnej" bazie danych, jest zatwierdzone i nie może zostać wycofane.

Pozwala to na uruchomienie jednej funkcji, która aktualizuje dużą tabelę w mniejszych częściach, a każda część jest zatwierdzana osobno. Unika narastania kosztów transakcji dla bardzo dużej liczby wierszy i, co ważniejsze, zwalnia blokady po każdej części. Pozwala to na prowadzenie równoległych operacji bez większych opóźnień i zmniejsza prawdopodobieństwo wystąpienia zakleszczeń.

Jeśli nie masz równoczesnego dostępu, jest to mało przydatne - z wyjątkiem unikania ROLLBACK po wyjątku. Rozważ także SAVEPOINT w takim przypadku.

Zastrzeżenie

Przede wszystkim wiele małych transakcji jest w rzeczywistości droższych. To ma ​​sens tylko w przypadku dużych stołów . Słodki punkt zależy od wielu czynników.

Jeśli nie masz pewności, co robisz:pojedyncza transakcja jest bezpieczną metodą . Aby to działało poprawnie, współbieżne operacje na stole muszą działać. Na przykład:współbieżne zapisy może przenieść wiersz na partycję, która podobno jest już przetworzona. Lub jednoczesne odczyty mogą zobaczyć niespójne stany pośrednie. Ostrzeżono Cię.

Instrukcje krok po kroku

Najpierw należy zainstalować dodatkowy moduł dblink:

  • Jak używać (zainstalować) dblink w PostgreSQL?

Konfiguracja połączenia z dblink w dużej mierze zależy od konfiguracji klastra DB i obowiązujących zasad bezpieczeństwa. To może być trudne. Powiązana później odpowiedź z więcej jak połączyć się z dblink :

  • Trwałe wstawianie w UDF, nawet jeśli funkcja przerywa

Utwórz FOREIGN SERVER i USER MAPPING zgodnie z instrukcjami, aby uprościć i usprawnić połączenie (chyba że już je masz).
Zakładając serial PRIMARY KEY z lukami lub bez nich.

CREATE OR REPLACE FUNCTION f_update_in_steps()
  RETURNS void AS
$func$
DECLARE
   _step int;   -- size of step
   _cur  int;   -- current ID (starting with minimum)
   _max  int;   -- maximum ID
BEGIN
   SELECT INTO _cur, _max  min(order_id), max(order_id) FROM orders;
                                        -- 100 slices (steps) hard coded
   _step := ((_max - _cur) / 100) + 1;  -- rounded, possibly a bit too small
                                        -- +1 to avoid endless loop for 0
   PERFORM dblink_connect('myserver');  -- your foreign server as instructed above

   FOR i IN 0..200 LOOP                 -- 200 >> 100 to make sure we exceed _max
      PERFORM dblink_exec(
       $$UPDATE public.orders
         SET    status = 'foo'
         WHERE  order_id >= $$ || _cur || $$
         AND    order_id <  $$ || _cur + _step || $$
         AND    status IS DISTINCT FROM 'foo'$$);  -- avoid empty update

      _cur := _cur + _step;

      EXIT WHEN _cur > _max;            -- stop when done (never loop till 200)
   END LOOP;

   PERFORM dblink_disconnect();
END
$func$  LANGUAGE plpgsql;

Zadzwoń:

SELECT f_update_in_steps();

Możesz sparametryzować dowolną część zgodnie ze swoimi potrzebami:nazwę tabeli, nazwę kolumny, wartość, ... po prostu pamiętaj o oczyszczeniu identyfikatorów, aby uniknąć wstrzyknięcia SQL:

  • Nazwa tabeli jako parametr funkcji PostgreSQL

Unikaj pustych aktualizacji:

  • Jak (lub mogę) WYBRAĆ ODRÓŻNIENIE w wielu kolumnach?


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Dlaczego Postgres nie korzysta z indeksu?

  2. Inny db do testowania w Django?

  3. Zwróć wiele pól jako rekord w PostgreSQL z PL/pgSQL

  4. Nie udało się znaleźć funkcji konwersji z nieznanej na tekst

  5. Narzędzia do generowania diagramów tabel bazy danych za pomocą PostgreSQL?