Czy aktualizacja wiersza o tej samej wartości faktycznie aktualizuje wiersz?

28

Mam pytanie związane z wydajnością. Załóżmy, że mam użytkownika o imieniu Michael. Weź następujące zapytanie:

UPDATE users
SET first_name = 'Michael'
WHERE users.id = 123

Czy zapytanie faktycznie wykona aktualizację, nawet jeśli jest aktualizowana do tej samej wartości? Jeśli tak, jak mogę temu zapobiec?

OneSneakyMofo
źródło
1
Dlaczego miałbyś wykonać instrukcję i jednocześnie oczekiwać, że się nie wykona?
Max Vernon,
@MaxVernon Ruby on Rails 'ORM nie aktualizuje rekordu, więc byłem ciekawy, czy PostgreSQL zrobił to samo.
OneSneakyMofo
1
Sugerowałbym, że jeśli Ruby on Rails to robi, to prawdopodobnie najpierw wybiera, aby sprawdzić, czy wiersz wymaga aktualizacji.
Max Vernon
wysłano x na SO: stackoverflow.com/q/33156712/939860
Erwin Brandstetter

Odpowiedzi:

35

Ze względu na model Postgres MVCC i zgodnie z regułami SQL, UPDATEdla każdego wiersza zapisuje nową wersję wiersza, która nie jest wykluczona w WHEREklauzuli.

Ma to mniej lub bardziej znaczący wpływ na wydajność, bezpośrednio i pośrednio. „Puste aktualizacje” mają taki sam koszt na wiersz jak każda inna aktualizacja. Wystrzeliwują wyzwalacze (jeśli są obecne), jak każda inna aktualizacja, muszą być zalogowane w WAL i wytwarzają martwe wiersze nadymające tabelę i powodujące więcej pracy na VACUUMpóźniej, jak każda inna aktualizacja.

Indeksuje wpisy i kolumny TOASTed, w których żadna z zaangażowanych kolumn nie jest zmieniana, nie może pozostać taka sama, ale dotyczy to każdego zaktualizowanego wiersza. Związane z:

Prawie zawsze dobrym pomysłem jest wykluczenie takich pustych aktualizacji (gdy istnieje rzeczywista szansa, że ​​może się zdarzyć). W swoim pytaniu nie podałeś definicji tabeli (co zawsze jest dobrym pomysłem). Musimy założyć, że first_namemoże być NULL (co nie byłoby zaskoczeniem dla „imienia”), dlatego zapytanie musi używać porównania NULL-safe :

UPDATE users
SET    first_name = 'Michael'
WHERE  id = 123
AND   first_name IS DISTINCT FROM 'Michael';

Jeśli first_name IS NULLprzed aktualizacją, test z just first_name <> 'Michael'będzie miał wartość NULL i jako taki wykluczy wiersz z aktualizacji. Podstępny błąd. Jeśli kolumna jest zdefiniowanaNOT NULL , użyj prostej kontroli równości, ponieważ jest to nieco tańsze.

Związane z:

Erwin Brandstetter
źródło
1
Indexes entries and TOASTed columns where none of the involved columns are changed can stay the sameAle czy nie trzeba ich aktualizować, aby wskazywały nową lokalizację rzędu?
dvtan
1
@dtgq: Nie w przypadku aktualizacji HOT, gdzie indeks może nadal wskazywać starą lokalizację, a pobrania sterty muszą przechodzić przez łańcuch HOT, aby uzyskać krotkę na żywo. Dodałem linki do dodatkowych wyjaśnień powyżej.
Erwin Brandstetter,
1
Co z wezwaniami MVCC do aktualizacji noop w celu napisania nowej krotki?
jberryman
@jberryman: Nie jestem pewien, czy rozumiem. Tak czy inaczej, zadaj pytanie jako nowe pytanie . Zawsze możesz utworzyć link do tego kontekstu. Możesz tu zostawić komentarz, aby utworzyć link z powrotem (i zwrócić moją uwagę).
Erwin Brandstetter
2
@jberryman: Tak naprawdę nie znam powodów, dla których projekt poszedł w ten sposób. To zostało ustalone dawno temu. Ale zakładam , że sprawdzanie równości każdego wiersza byłoby niepotrzebnie kosztowne i mieć osobną ścieżkę do kodu dla niezmienionych wierszy. Obsługa identyfikatorów transakcji byłaby bardziej skomplikowana - specjalna obudowa rollback, obsługa migawek, zarządzanie zamkami, WAL, co nie ...
Erwin Brandstetter
4

ORM, podobnie jak oferta Ruby on Rail, odracza wykonanie, które oznacza rekord jako zmieniony (lub nie), a następnie w razie potrzeby lub wywołania, a następnie przesyła zmianę do bazy danych.

PostgreSQL to baza danych, a nie ORM. Zmniejszyłoby to wydajność, gdyby zajęło trochę czasu sprawdzenie, czy nowa wartość jest taka sama jak zaktualizowana wartość w zapytaniu.

W związku z tym zaktualizuje wartość bez względu na to, czy jest taka sama jak nowa wartość, czy nie.

Jeśli chcesz temu zapobiec, możesz użyć kodu takiego jak sugerowany w odpowiedzi Max Vernon.

Thronk
źródło
2

Możesz po prostu dodać do whereklauzuli:

UPDATE users
SET first_name = 'Michael'
WHERE users.id = 123
    AND (first_name <> 'Michael' OR first_name IS NULL);

Jeśli first_namezdefiniowano jako NOT NULL, OR first_name IS NULLczęść można usunąć.

Warunek:

(first_name <> 'Michael' OR first_name IS NULL)

można również napisać bardziej elegancko, ponieważ (w odpowiedzi Erwina):

first_name IS DISTINCT FROM 'Michael'
Max Vernon
źródło
Nie wiedząc, czy kolumna może mieć wartość NULL, może wprowadzić podstępny błąd.
Erwin Brandstetter
1
@ErwinBrandstetter Aktualizowałem odpowiedź - wtedy zobaczyłem komentarz i twoją odpowiedź!
ypercubeᵀᴹ
dzięki za edycję, @ypercube - i za komentarz na temat NULL@erwin
Max Vernon
1

Z punktu widzenia bazy danych

Odpowiedź na twoje pytanie brzmi TAK. Aktualizacja odbędzie się. Baza danych nie sprawdza poprzedniej wartości, ustawia tylko nową wartość.

Ponieważ dzieje się to w pamięci (i zostanie zapisane w plikach danych dopiero po wydaniu zatwierdzenia), wydajność nie będzie stanowić problemu.

Z perspektywy ORM

Zwykle będziesz mieć Obiekt reprezentujący pojedynczy wiersz bazy danych (może być o wiele bardziej złożony, ale bądźmy prostsze). Ten obiekt jest zarządzany w pamięci (na poziomie serwera aplikacji) i tylko najnowsza zatwierdzona wersja tego obiektu faktycznie trafi do bazy danych w pewnym momencie.

To może tłumaczyć inne zachowanie.

Nie porównujmy teraz statku towarowego z drukarką 3D. Fakt, że możesz wysyłać drukarki 3D za pomocą statków towarowych, nie oznacza, że ​​może istnieć jakiekolwiek porównanie między nimi.

Cieszyć się!

Mam nadzieję, że wyjaśniło to niektóre pojęcia.

Silvarion
źródło
4
Wydajność jest i problem. Każda aktualizacja musi być zapisana na dysku (dziennik i tabela).
ypercubeᵀᴹ
Będzie to zależeć od faktycznego używanego RDBMS. Ale większość z nich nie zatwierdza każdej aktualizacji, a jedynie ostatni zatwierdzony blok, który mają w pamięci. Nigdy nie czytasz ani nie piszesz jednego wiersza w bazie danych. Odczytujesz / zapisujesz bloki i przechowujesz je w pamięci, aż będziesz musiał je wypłukać, aby umieścić nowy blok w tym samym miejscu. W pamięci nie każda zmiana z rzędu będzie zapisywana na dysk, ale tylko zawartość bloku, gdy proces „zapisywania bazy danych” zostanie zasygnalizowany, aby zrzucić ten blok pamięci do pliku danych. Więc nie ... Nie stanowi problemu, chyba że twoja aplikacja zbyt długo blokuje niezaangażowane.
Silvarion
1
pytanie dotyczy Postgres, a nie dowolnego DBMS. I chociaż nie wszystkie aktualizacje muszą być zapisywane jeden po drugim, każdy zapis w bazie danych musi być zapisany w dzienniku. Jeśli zmiana nie zostanie zapisana w pamięci trwałej, w jaki sposób DBMS przetrwa awarię systemu?
ypercubeᵀᴹ
Tak, zapisuje w dziennikach, również z pamięci podczas punktów kontrolnych. Jeśli nie masz strasznie dużej liczby równoczesnych użytkowników, nie powinno to stanowić problemu. Logi zapisywane są również partiami. Myślę, że mówimy o serwerach. Jeśli mówisz o bazie danych Postgres w laptopie z dyskiem twardym 5400 RPM, tak ... zawsze będziesz mieć problemy z wydajnością. Ostateczna odpowiedź byłaby pierwsza… To zależy od zbyt wielu rzeczy.
Silvarion