W Postgres mamy tabelę 2,2 GB z 7 801 611 wierszami. Dodajemy do niej kolumnę uuid / guid i zastanawiam się, jaki jest najlepszy sposób na wypełnienie tej kolumny (ponieważ chcemy dodać NOT NULL
do niej ograniczenie).
Jeśli dobrze rozumiem Postgres, aktualizacja jest technicznie usunięciem i wstawieniem, więc zasadniczo przebudowuje całą tabelę 2,2 GB. Mamy też działającego niewolnika, więc nie chcemy, aby pozostawało w tyle.
Czy jest coś lepszego niż pisanie skryptu, który powoli wypełnia go z czasem?
postgresql
storage
ddl
Collin Peters
źródło
źródło
ALTER TABLE .. ADD COLUMN ...
czy też na tę część należy odpowiedzieć?Odpowiedzi:
To bardzo zależy od szczegółów twoich wymagań.
Jeśli masz wystarczającą ilość wolnego miejsca (co najmniej 110%
pg_size_pretty((pg_total_relation_size(tbl))
) na dysku i możesz sobie pozwolić na blokadę udostępniania przez pewien czas i blokadę wyłączności na bardzo krótki czas , to utwórz nową tabelę zawierającąuuid
kolumnę za pomocąCREATE TABLE AS
. Czemu?Poniższy kod wykorzystuje funkcję z dodatkowego
uuid-oss
modułu .Zablokuj tabelę przed jednoczesnymi zmianami w
SHARE
trybie (nadal pozwalając na jednoczesne odczyty). Próby zapisu do tabeli będą czekać i ostatecznie zakończą się niepowodzeniem. Patrz poniżej.Skopiuj całą tabelę podczas wypełniania nowej kolumny w locie - możliwe, że porządnie porządkuje rzędy, będąc przy niej.
Jeśli zamierzasz zmienić kolejność wierszy, pamiętaj, aby ustawić
work_mem
możliwie najwyższą wartość (tylko na sesję, a nie globalnie).Następnie dodaj ograniczenia, klucze obce, indeksy, wyzwalacze itp. Do nowej tabeli. Podczas aktualizacji dużych części tabeli tworzenie indeksów od zera jest znacznie szybsze niż iteracyjne dodawanie wierszy.
Gdy nowy stół będzie gotowy, upuść stary i zmień jego nazwę, aby zastąpić go nowym. Tylko ten ostatni krok zyskuje wyłączną blokadę na starym stole do końca transakcji - która powinna być teraz bardzo krótka.
Wymaga to również usunięcia dowolnego obiektu w zależności od typu tabeli (widoki, funkcje wykorzystujące typ tabeli w podpisie, ...), a następnie odtworzenia ich później.
Zrób to wszystko w jednej transakcji, aby uniknąć niekompletnych stanów.
To powinno być najszybsze. Każda inna metoda aktualizacji musi również przepisać cały stół, tylko w droższy sposób. Wybrałbyś tę trasę tylko wtedy, gdy nie masz wystarczającej ilości wolnego miejsca na dysku lub nie możesz sobie pozwolić na zablokowanie całej tabeli lub wygenerowanie błędów dla równoczesnych prób zapisu.
Co dzieje się z jednoczesnymi zapisami?
Inna transakcja (w innych sesjach), która próbuje
INSERT
/UPDATE
/DELETE
w tej samej tabeli po tym, jak transakcja przejęłaSHARE
blokadę, będzie czekać na zwolnienie blokady lub przekroczenie limitu czasu, zależnie od tego, co nastąpi wcześniej. I tak się nie powiedzie , ponieważ tabela, do której próbowali pisać, została z nich usunięta.Nowa tabela ma nowy identyfikator OID tabeli, ale współbieżna transakcja już rozwiązała nazwę tabeli na identyfikator OID poprzedniej tabeli . Kiedy blokada jest w końcu zwolniona, próbują sami zablokować stół przed napisaniem do niego i stwierdzają, że zniknął. Postgres odpowie:
Gdzie
123456
jest OID starej tabeli. Musisz złapać ten wyjątek i ponowić zapytania w kodzie aplikacji, aby go uniknąć.Jeśli nie możesz sobie na to pozwolić, musisz zachować swój oryginalny stół.
Dwie alternatywy utrzymujące istniejący stół
Zaktualizuj na miejscu (możliwe, że aktualizację uruchamiasz jednocześnie na małych segmentach) przed dodaniem
NOT NULL
ograniczenia. Dodanie nowej kolumny z wartościami NULL i bezNOT NULL
ograniczeń jest tanie.Od wersji Postgres 9.2 możesz również utworzyć
CHECK
ograniczenie za pomocąNOT VALID
:Umożliwia to aktualizację wierszy peu à peu - w wielu osobnych transakcjach . Pozwala to uniknąć zbyt długiego blokowania wierszy, a także umożliwia ponowne użycie martwych wierszy. (Będziesz musiał uruchomić
VACUUM
ręcznie, jeśli nie ma wystarczającej ilości czasu pomiędzy uruchomieniem autovacuum.) Na koniec dodajNOT NULL
ograniczenie i usuńNOT VALID CHECK
ograniczenie:Powiązana odpowiedź omawiająca
NOT VALID
bardziej szczegółowo:Przygotuj nowy stan w tabeli tymczasowej ,
TRUNCATE
oryginalnej i uzupełnij z tabeli tymczasowej. Wszystko w jednej transakcji . Nadal musiszSHARE
zablokować się przed przygotowaniem nowego stołu, aby zapobiec utracie równoczesnych zapisów.Szczegóły w powiązanej odpowiedzi dotyczącej SO:
źródło
LOCK
włączania i wyłączaniaDROP
. Mogłem tylko wypowiedzieć dzikie i bezużyteczne domysły. Jeśli chodzi o 2., proszę rozważyć dodatek do mojej odpowiedzi.Nie mam „najlepszej” odpowiedzi, ale mam „najmniej złą” odpowiedź, która może pozwolić ci zrobić wszystko dość szybko.
Moja tabela miała 2MM wiersze, a wydajność aktualizacji była chugująca, gdy próbowałem dodać dodatkową kolumnę znacznika czasu, która domyślnie była ustawiona na pierwszą.
Po zawieszeniu przez 40 minut spróbowałem tego na małej partii, aby dowiedzieć się, ile to może potrwać - prognoza wynosiła około 8 godzin.
Akceptowana odpowiedź jest zdecydowanie lepsza - ale ta tabela jest bardzo używana w mojej bazie danych. Jest tam kilkadziesiąt tabel, które FKEY na nim; Chciałem uniknąć przełączania KLUCZY ZAGRANICZNYCH na tak wielu stołach. A potem są poglądy.
Trochę przeszukiwania dokumentów, studiów przypadków i StackOverflow, a ja miałem „A-Ha!” za chwilę. Odpływ nie dotyczył podstawowej aktualizacji, ale wszystkich operacji INDEX. Moja tabela miała 12 indeksów - kilka dla unikalnych ograniczeń, kilka dla przyspieszenia planowania zapytań i kilka dla wyszukiwania pełnotekstowego.
Każdy wiersz, który został zaktualizowany, nie tylko działał na DELETE / INSERT, ale także narzut związany ze zmianą każdego indeksu i sprawdzaniem ograniczeń.
Moim rozwiązaniem było usunięcie każdego indeksu i ograniczenia, zaktualizowanie tabeli, a następnie dodanie wszystkich indeksów / ograniczeń z powrotem.
Napisanie transakcji SQL, która wykonała następujące czynności, zajęło około 3 minut:
Uruchomienie skryptu zajęło 7 minut.
Przyjęta odpowiedź jest zdecydowanie lepsza i bardziej właściwa ... i praktycznie eliminuje potrzebę przestojów. W moim przypadku użycie tego rozwiązania wymagałoby znacznie więcej pracy „programisty”, a my mieliśmy 30-minutowy okres planowego przestoju, w którym można go zrealizować. Nasze rozwiązanie rozwiązało to w 10.
źródło