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?