Jeśli chodzi o aktualizację wiersza, wiele narzędzi ORM wydaje instrukcję UPDATE, która ustawia każdą kolumnę powiązaną z tym konkretnym podmiotem .
Zaletą jest to, że można łatwo grupować instrukcje aktualizacji, ponieważ UPDATE
instrukcja jest taka sama bez względu na zmieniony atrybut encji. Co więcej, możesz nawet używać buforowania instrukcji po stronie serwera i klienta.
Jeśli więc załaduję jednostkę i ustawię tylko jedną właściwość:
Post post = entityManager.find(Post.class, 1L);
post.setScore(12);
Wszystkie kolumny zostaną zmienione:
UPDATE post
SET score = 12,
title = 'High-Performance Java Persistence'
WHERE id = 1
Teraz, zakładając, że mamy również indeks title
nieruchomości, czyż DB nie powinien zdawać sobie sprawy, że i tak wartość się nie zmieniła?
W tym artykule Markus Winand mówi:
Aktualizacja we wszystkich kolumnach pokazuje ten sam wzorzec, który już zaobserwowaliśmy w poprzednich sekcjach: czas odpowiedzi rośnie z każdym dodatkowym indeksem.
Zastanawiam się, dlaczego jest to narzut, ponieważ baza danych ładuje powiązaną stronę danych z dysku do pamięci, dzięki czemu może dowiedzieć się, czy należy zmienić wartość kolumny.
Nawet w przypadku indeksów nie zmienia to niczego, ponieważ wartości indeksów nie zmieniają się dla kolumn, które nie uległy zmianie, ale zostały uwzględnione w AKTUALIZACJI.
Czy to dlatego, że indeksy drzewa B + powiązane z nadmiarowymi niezmienionymi kolumnami również wymagają nawigacji, tylko po to, aby baza danych zdała sobie sprawę, że wartość liścia jest nadal taka sama?
Oczywiście niektóre narzędzia ORM umożliwiają aktualizację tylko zmienionych właściwości:
UPDATE post
SET score = 12,
WHERE id = 1
Ale ten typ aktualizacji może nie zawsze korzystać z aktualizacji wsadowych lub buforowania instrukcji, gdy różne właściwości są zmieniane dla różnych wierszy.
źródło
UPDATE
jest praktycznie równoznaczne zDELETE
+INSERT
(bo rzeczywiście stworzyć nową V ersion wiersza). Koszty ogólne są wysokie i rosną wraz z liczbą indeksów , zwłaszcza jeśli wiele kolumn, które je zawierają, są faktycznie aktualizowane, a drzewo (lub cokolwiek innego) użyte do reprezentacji indeksu wymaga znacznej zmiany. Istotna jest nie liczba kolumn, które są aktualizowane, ale to, czy aktualizujesz kolumnę jako część indeksu.Odpowiedzi:
Wiem, że najbardziej martwisz się
UPDATE
głównie wydajnością, ale jako opiekun „ORM” pozwól, że przedstawię Ci inne spojrzenie na problem rozróżnienia między wartościami „zmienionymi” , „zerowymi” i „domyślnymi” , które są trzy różne rzeczy w SQL, ale prawdopodobnie tylko jedna rzecz w Javie i większości ORM:Tłumaczenie uzasadnienia na
INSERT
stwierdzeniaTwoje argumenty na rzecz batchowalności i buforowania instrukcji zachowują się tak samo w przypadku
INSERT
instrukcji, jak i w przypadkuUPDATE
instrukcji. Ale w przypadkuINSERT
instrukcji pominięcie kolumny w instrukcji ma inną semantykę niż wUPDATE
. Oznacza zastosowanieDEFAULT
. Następujące dwa są semantycznie równoważne:Nie dotyczy to sytuacji
UPDATE
, gdy pierwsze dwa są semantycznie równoważne, a trzeci ma zupełnie inne znaczenie:Większość interfejsów API klienta bazy danych, w tym JDBC, aw konsekwencji JPA, nie pozwala na wiązanie
DEFAULT
wyrażenia ze zmienną powiązania - głównie dlatego, że serwery też na to nie pozwalają. Jeśli chcesz ponownie użyć tej samej instrukcji SQL z wyżej wymienionych powodów wsadowości i buforowania instrukcji, możesz użyć następującej instrukcji w obu przypadkach (zakładając, że(a, b, c)
są to wszystkie kolumnyt
):A ponieważ
c
nie jest ustawiony, prawdopodobnie powiążesz Javęnull
z trzecią zmienną powiązania, ponieważ wiele ORM również nie może rozróżnić międzyNULL
iDEFAULT
( jOOQ , na przykład będący tutaj wyjątkiem). Widzą tylko Javęnull
i nie wiedzą, czy to oznaczaNULL
(jak w nieznanej wartości) czyDEFAULT
(jak w niezainicjowanej wartości).W wielu przypadkach to rozróżnienie nie ma znaczenia, ale jeśli kolumna c używa jednej z następujących funkcji, stwierdzenie jest po prostu błędne :
DEFAULT
klauzulęPowrót do
UPDATE
wyciągówChociaż powyższe dotyczy wszystkich baz danych, zapewniam cię, że problem z wyzwalaczem dotyczy również bazy danych Oracle. Rozważ następujący kod SQL:
Po uruchomieniu powyższego zobaczysz następujące dane wyjściowe:
Jak widać, instrukcja, która zawsze aktualizuje wszystkie kolumny, zawsze wyzwala wyzwalacz dla wszystkich kolumn, natomiast instrukcje aktualizujące tylko kolumny, które uległy zmianie, uruchamiają tylko te wyzwalacze, które oczekują takich konkretnych zmian.
Innymi słowy:
Obecne zachowanie Hibernacji, które opisujesz, jest niekompletne i może nawet zostać uznane za niewłaściwe w obecności wyzwalaczy (i prawdopodobnie innych narzędzi).
Osobiście uważam, że argument optymalizacji pamięci podręcznej zapytania jest przereklamowany w przypadku dynamicznego SQL. Jasne, w takiej pamięci podręcznej będzie jeszcze kilka zapytań i trochę więcej analizowania, ale zwykle nie stanowi to problemu dla
UPDATE
instrukcji dynamicznych , a znacznie mniej niż dlaSELECT
.Batching jest z pewnością problemem, ale moim zdaniem pojedyncza aktualizacja nie powinna być znormalizowana, aby zaktualizować wszystkie kolumny tylko dlatego, że istnieje niewielka możliwość, że instrukcja będzie batchowalna. Możliwe, że ORM może zbierać podgrupy kolejnych identycznych instrukcji i grupować je zamiast „całej partii” (w przypadku, gdy ORM jest w stanie nawet śledzić różnicę między „zmienioną” , „zerową” i „domyślną”
źródło
DEFAULT
Przypadek użycia może być skierowana przez@DynamicInsert
. Sytuację TRIGGER można również rozwiązać za pomocą czeków takich jakWHEN (NEW.b <> OLD.b)
lub po prostu przełącz na@DynamicUpdate
.Myślę, że odpowiedź brzmi - to skomplikowane . Próbowałem napisać szybki dowód przy użyciu
longtext
kolumny w MySQL, ale odpowiedź jest trochę niejednoznaczna. Dowód pierwszy:Tak więc istnieje niewielka różnica czasu między wolną + zmienioną wartością, a wolną + niezmienioną wartością. Postanowiłem więc spojrzeć na inną metrykę, którą były napisane strony:
Wygląda więc na to, że czas się wydłużył, ponieważ musi istnieć porównanie, aby potwierdzić, że sama wartość nie została zmodyfikowana, co w przypadku długiego tekstu 1G wymaga czasu (ponieważ jest podzielony na wiele stron). Ale sama modyfikacja nie wydaje się przesuwać w dzienniku powtórzeń.
Podejrzewam, że jeśli wartości są zwykłymi kolumnami, które znajdują się na stronie, porównanie powoduje tylko niewielki narzut. I przy założeniu, że obowiązuje ta sama optymalizacja, nie ma żadnych przestojów, jeśli chodzi o aktualizację.
Dłuższa odpowiedź
Myślę, że ORM nie powinien eliminować kolumn, które zostały zmodyfikowane ( ale nie zmienione ), ponieważ ta optymalizacja ma dziwne skutki uboczne.
W pseudo-kodzie rozważ następujące kwestie:
Wynik, jeśli ORM miałby „zoptymalizować” modyfikację bez zmian:
Wynik, jeśli ORM wyśle wszystkie modyfikacje do serwera:
Przypadek testowy tutaj opiera się na
repeatable-read
izolacji (domyślny MySQL), ale istnieje również okno czasowe dlaread-committed
izolacji, w której odczyt sesji2 następuje przed zatwierdzeniem sesji1.Innymi słowy: optymalizacja jest bezpieczna tylko wtedy, gdy wydasz a,
SELECT .. FOR UPDATE
aby odczytać wiersze, a następnieUPDATE
.SELECT .. FOR UPDATE
nie używa MVCC i zawsze czyta najnowszą wersję wierszy.Edycja: Upewnij się, że zestaw danych przypadku testowego ma 100% pamięci. Skorygowane wyniki czasowe.
źródło