SQL Server zapewnia dwie fizyczne implementacje zatwierdzonego odczytu poziom izolacji zdefiniowany przez standard SQL, blokowanie odczytu zatwierdzonej i odczytu zatwierdzonej migawki migawki (RCSI ). Chociaż obie implementacje spełniają wymagania określone w standardzie SQL dla zachowań związanych z izolacją popełnioną podczas odczytu, RCSI ma zupełnie inne fizyczne zachowania niż implementacja blokowania, którą omówiliśmy w poprzednim poście z tej serii.
Gwarancje logiczne
Standard SQL wymaga, aby transakcja działająca na poziomie izolacji zatwierdzonej do odczytu nie doświadczała żadnych nieczystych odczytów. Innym sposobem wyrażenia tego wymagania jest powiedzenie, że transakcja zatwierdzona do odczytu musi tylko napotkać zatwierdzone dane .
Standard mówi również, że odczyt zatwierdzonych transakcji może doświadczaj zjawiska współbieżności znanego jako niepowtarzalne odczyty i fantomy (choć w rzeczywistości nie są do tego wymagane). Tak się składa, że obie fizyczne implementacje odczytu zatwierdzonej izolacji w SQL Server mogą doświadczać niepowtarzalnych odczytów i wierszy fantomowych, chociaż dokładne szczegóły są zupełnie inne.
Widok z określonego punktu w czasie zatwierdzonych danych
Jeśli opcja bazy danych READ_COMMITTED_SNAPSHOT
w ON
, SQL Server używa implementacji obsługi wersji wierszy odczytu zatwierdzonego poziomu izolacji. Gdy ta opcja jest włączona, transakcje żądające odczytu zatwierdzonej izolacji automatycznie używają implementacji RCSI; do korzystania z RCSI nie są wymagane żadne zmiany w istniejącym kodzie T-SQL. Pamiętaj jednak, że to to nie to samo mówiąc, że kod zachowuje się tak samo pod RCSI, tak jak przy użyciu implementacji blokowania odczytu popełnionego, w rzeczywistości jest to dość ogólnie nie w tym przypadku .
W standardzie SQL nie ma nic, co wymagałoby, aby dane odczytywane przez zatwierdzoną transakcję odczytu były najnowszymi popełnione dane. Implementacja SQL Server RCSI wykorzystuje to, aby zapewnić transakcjom widok z punktu w czasie zatwierdzonych danych, gdzie ten moment jest momentem, w którym rozpoczęło się bieżące oświadczenie wykonanie (nie w momencie rozpoczęcia jakiejkolwiek zawierającej transakcji).
Jest to zupełnie inne niż zachowanie implementacji blokowania SQL Server w przypadku odczytu zatwierdzonego, w której instrukcja widzi ostatnio zatwierdzone dane od momentu, w którym każdy element jest fizycznie odczytywany . Zablokowanie odczytanych zatwierdzonych wersji zwalnia współdzielone blokady tak szybko, jak to możliwe, więc napotkany zestaw danych może pochodzić z bardzo różnych punktów w czasie.
Podsumowując, blokowanie odczytu zatwierdzonego powoduje wyświetlenie każdego wiersza tak jak w tamtym czasie był na krótko zablokowany i fizycznie odczytany; RCSI widzi wszystkie wiersze tak jak w chwili, gdy rozpoczęło się oświadczenie. Obie implementacje gwarantują, że nigdy nie zobaczą niezatwierdzonych danych, ale dane, które napotykają, mogą być bardzo różne.
Implikacje punktu w czasie
Widzenie punktu w czasie zatwierdzonych danych może wydawać się oczywiście lepsze od bardziej złożonego zachowania implementacji blokowania. Oczywiste jest na przykład, że widok z określonego punktu w czasie nie może cierpieć z powodu problemów brakujących wierszy lub napotykając ten sam wiersz wiele razy , które są możliwe w przypadku blokady odczytu popełnionej izolacji.
Drugą ważną zaletą RCSI jest to, że nie uzyskuje wspólnych blokad podczas odczytywania danych, ponieważ dane pochodzą z magazynu wersji wiersza, a nie są dostępne bezpośrednio. Brak wspólnych blokad może radykalnie poprawić współbieżność eliminując konflikty z równoczesnymi transakcjami, które mają na celu uzyskanie niekompatybilnych blokad. Ta zaleta jest często podsumowana stwierdzeniem, że czytelnicy nie blokują pisarzy w RCSI i na odwrót. Kolejną konsekwencją zmniejszenia blokowania z powodu niezgodnych żądań zablokowania jest możliwość zakleszczenia jest zwykle znacznie zmniejszona, gdy działa w RCSI.
Jednak te korzyści nie są pozbawione kosztów i zastrzeżeń . Po pierwsze, utrzymywanie wersji zatwierdzonych wierszy pochłania zasoby systemowe, dlatego ważne jest, aby środowisko fizyczne było skonfigurowane tak, aby sobie z tym poradzić, głównie pod kątem tempdb wymagania dotyczące wydajności i pamięci/miejsca na dysku.
Drugie zastrzeżenie jest nieco bardziej subtelne:RCSI zapewnia widok migawki zatwierdzonych danych tak jak było na początku instrukcji, ale nic nie stoi na przeszkodzie, aby rzeczywiste dane zostały zmienione (i te zmiany zatwierdzone) podczas wykonywania instrukcji RCSI. Pamiętaj, że nie ma wspólnych blokad. Bezpośrednią konsekwencją tego drugiego punktu jest to, że kod T-SQL działający pod kontrolą RCSI może podejmować decyzje na podstawie nieaktualnych informacji , w porównaniu z bieżącym zatwierdzonym stanem bazy danych. Porozmawiamy o tym wkrótce.
Jest jeszcze jedna ostatnia (specyficzna dla implementacji) obserwacja, którą chcę poczynić na temat RCSI, zanim przejdziemy dalej. Funkcje skalarne i wielowyrazowe wykonaj przy użyciu innego wewnętrznego kontekstu T-SQL niż instrukcja zawierająca. Oznacza to, że widok punktu w czasie widoczny w wywołaniu funkcji skalarnej lub wieloinstrukcji może być późniejszy niż widok punktu w czasie widoczny w pozostałej części instrukcji. Może to spowodować nieoczekiwane niespójności, ponieważ różne części tego samego stwierdzenia wyświetlają dane z różnych punktów w czasie . To dziwne i mylące zachowanie nie mają zastosowanie do funkcji wbudowanych, które widzą ten sam zrzut obrazu, co instrukcja, w której się pojawiają.
Niepowtarzalne odczyty i fantomy
Mając widok stanu bazy danych z punktu w czasie na poziomie instrukcji, może nie być od razu oczywiste, w jaki sposób odczytana transakcja zatwierdzona w RCSI może doświadczać niepowtarzalnych zjawisk odczytu lub wiersza fantomowego. Rzeczywiście, jeśli ograniczymy nasze myślenie do zakresu jednego stwierdzenia , żadne z tych zjawisk nie jest możliwe w RCSI.
Wielokrotne odczytywanie tych samych danych w ramach tego samego oświadczenia pod RCSI zawsze zwróci te same wartości danych, żadne dane nie znikną między tymi odczytami i nie pojawią się również żadne nowe dane. Jeśli zastanawiasz się, jaki rodzaj instrukcji może odczytywać te same dane więcej niż raz, pomyśl o zapytaniach, które odwołują się do tej samej tabeli więcej niż raz, być może w podzapytaniu.
Spójność odczytu na poziomie instrukcji jest oczywistą konsekwencją odczytów wykonywanych względem stałej migawki danych. Powód, dla którego RCSI nie zapewnienie ochrony przed niepowtarzalnymi odczytami i fantomami polega na tym, że te standardowe zjawiska SQL są definiowane na poziomie transakcji. Wiele wyciągów w ramach transakcji uruchomionej w RCSI może wyświetlać różne dane, ponieważ każdy wyciąg wyświetla widok z określonego punktu w czasie na moment tego konkretnego wyciągu rozpoczęte.
Podsumowując, każde oświadczenie w ramach transakcji RCSI widzi statyczny zatwierdzony zestaw danych, ale ten zestaw może się zmieniać między oświadczeniami w tej samej transakcji.
Nieaktualne dane
Możliwość podejmowania przez nasz kod T-SQL ważnych decyzji w oparciu o nieaktualne informacje jest bardziej niż trochę niepokojąca. Zastanów się przez chwilę, że migawka z określonego punktu w czasie używana przez pojedynczą instrukcję działającą w ramach RCSI może być dowolnie stara .
Instrukcja, która działa przez dłuższy czas, będzie nadal widzieć zatwierdzony stan bazy danych, tak jak w momencie rozpoczęcia instrukcji. Tymczasem w oświadczeniu brakuje wszystkich zatwierdzonych zmian, które miały miejsce w bazie danych od tego czasu.
Nie oznacza to, że problemy związane z dostępem do nieaktualnych danych w ramach RCSI ograniczają się do długotrwałego oświadczenia, ale z pewnością problemy mogą być bardziej wyraźne w takich przypadkach.
Kwestia czasu
Ten problem nieaktualnych danych dotyczy w zasadzie wszystkich oświadczeń RCSI, bez względu na to, jak szybko mogą zostać wypełnione. Niezależnie od tego, jak małe jest okno czasowe, zawsze istnieje szansa, że współbieżna operacja może zmodyfikować zestaw danych, z którym pracujemy, bez naszej świadomości tej zmiany. Przyjrzyjmy się ponownie jednemu z prostych przykładów, których używaliśmy wcześniej, badając zachowanie blokowania odczytu popełnionego:
INSERT dbo.OverdueInvoices SELECT I.InvoiceNumber FROM dbo.Invoices AS I WHERE I.TotalDue > ( SELECT SUM(P.Amount) FROM dbo.Payments AS P WHERE P.InvoiceNumber = I.InvoiceNumber );
W przypadku uruchomienia w ramach RCSI to stwierdzenie nie może zobacz wszelkie zatwierdzone modyfikacje bazy danych, które wystąpią po rozpoczęciu wykonywania instrukcji. Chociaż nie napotkamy problemów z pominiętymi lub wielokrotnymi napotkanymi wierszami możliwymi w ramach implementacji blokowania, równoczesna transakcja może dodać płatność, która powinna aby zapobiec wysyłaniu klientowi surowego listu ostrzegawczego o zaległej płatności po rozpoczęciu realizacji powyższego oświadczenia.
Prawdopodobnie możesz pomyśleć o wielu innych potencjalnych problemach, które mogą wystąpić w tym scenariuszu lub w innych, które są koncepcyjnie podobne. Im dłużej trwa instrukcja, tym bardziej nieaktualny staje się jej widok bazy danych i tym większy jest zakres możliwych niezamierzonych konsekwencji.
Oczywiście w tym konkretnym przykładzie istnieje wiele czynników łagodzących. To zachowanie może być postrzegane jako całkowicie akceptowalne. W końcu wysłanie listu przypominającego, ponieważ płatność dotarła kilka sekund za późno, jest działaniem łatwym do obrony. Zasada pozostaje jednak.
Niepowodzenia reguł biznesowych i zagrożenia integralności
Korzystanie z nieaktualnych informacji może spowodować poważniejsze problemy niż wysłanie ostrzeżenia kilka sekund wcześniej. Dobrym przykładem tej klasy słabości jest kod wyzwalający służy do wymuszania reguły integralności, która jest prawdopodobnie zbyt złożona, aby można ją było wymusić z deklaratywnymi ograniczeniami integralności referencyjnej. Aby to zilustrować, rozważmy następujący kod, który używa wyzwalacza do wymuszenia odmiany ograniczenia klucza obcego, ale wymusza relację tylko dla niektórych wierszy tabeli podrzędnej:
ALTER DATABASE Sandpit SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE; GO SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO CREATE TABLE dbo.Parent (ParentID integer PRIMARY KEY); GO CREATE TABLE dbo.Child ( ChildID integer IDENTITY PRIMARY KEY, ParentID integer NOT NULL, CheckMe bit NOT NULL ); GO CREATE TRIGGER dbo.Child_AI ON dbo.Child AFTER INSERT AS BEGIN -- Child rows with CheckMe = true -- must have an associated parent row IF EXISTS ( SELECT ins.ParentID FROM inserted AS ins WHERE ins.CheckMe = 1 EXCEPT SELECT P.ParentID FROM dbo.Parent AS P ) BEGIN RAISERROR ('Integrity violation!', 16, 1); ROLLBACK TRANSACTION; END END; GO -- Insert parent row #1 INSERT dbo.Parent (ParentID) VALUES (1);
Teraz rozważ transakcję uruchomioną w innej sesji (użyj do tego innego okna SSMS, jeśli podążasz dalej), która usuwa nadrzędny wiersz nr 1, ale jeszcze nie zatwierdza:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; BEGIN TRANSACTION; DELETE FROM dbo.Parent WHERE ParentID = 1;
Wracając do naszej pierwotnej sesji, próbujemy wstawić (zaznaczony) wiersz podrzędny, który odwołuje się do tego rodzica:
INSERT dbo.Child (ParentID, CheckMe) VALUES (1, 1);
Kod wyzwalacza jest wykonywany, ale ponieważ RCSI widzi tylko zatwierdzone dane od momentu uruchomienia instrukcji, nadal widzi wiersz nadrzędny (nie niezatwierdzone usunięcie) i wstawienie powiodło się !
Transakcja, która usunęła wiersz nadrzędny, może teraz pomyślnie zatwierdzić zmianę, pozostawiając bazę danych w stanie niespójnym stan pod względem naszej logiki wyzwalania:
COMMIT TRANSACTION; SELECT P.* FROM dbo.Parent AS P; SELECT C.* FROM dbo.Child AS C;
Jest to oczywiście uproszczony przykład, który można łatwo obejść za pomocą wbudowanych funkcji ograniczających. O wiele bardziej złożone reguły biznesowe i ograniczenia pseudo-integralności można zapisać wewnątrz i poza wyzwalaczami . Możliwość nieprawidłowego zachowania w ramach RCSI powinna być oczywista.
Blokowanie i ostatnio wprowadzone dane
Wspomniałem wcześniej, że kod T-SQL nie ma gwarancji, że będzie zachowywał się w ten sam sposób przy zatwierdzeniu odczytu RCSI, jak przy użyciu implementacji blokowania. Powyższy przykład kodu wyzwalacza jest tego dobrą ilustracją, ale muszę podkreślić, że ogólny problem nie ogranicza się do wyzwalaczy .
RCSI zazwyczaj nie jest dobrym wyborem dla żadnego kodu T-SQL, którego poprawność zależy od blokowania, jeśli istnieje współbieżna niezatwierdzona zmiana. RCSI może również nie być właściwym wyborem, jeśli kod zależy od odczytu bieżącego zatwierdzonych danych, a nie najnowszych zatwierdzonych danych w momencie rozpoczęcia oświadczenia. Te dwie kwestie są ze sobą powiązane, ale nie są tym samym.
Blokowanie odczytu zatwierdzone w RCSI
SQL Server zapewnia jeden sposób żądania blokowania odczyt zatwierdzony, gdy RCSI jest włączony, korzystając ze wskazówki tabeli READCOMMITTEDLOCK
. Możemy zmodyfikować nasz wyzwalacz, aby uniknąć problemów pokazanych powyżej, dodając tę wskazówkę do tabeli, która wymaga zachowania blokującego, aby działać poprawnie:
ALTER TRIGGER dbo.Child_AI ON dbo.Child AFTER INSERT AS BEGIN -- Child rows with CheckMe = true -- must have an associated parent row IF EXISTS ( SELECT ins.ParentID FROM inserted AS ins WHERE ins.CheckMe = 1 EXCEPT SELECT P.ParentID FROM dbo.Parent AS P WITH (READCOMMITTEDLOCK) -- NEW!! ) BEGIN RAISERROR ('Integrity violation!', 16, 1); ROLLBACK TRANSACTION; END END;
Po wprowadzeniu tej zmiany próba wstawienia potencjalnie osieroconych bloków wierszy podrzędnych do momentu zatwierdzenia (lub przerwania) usuwającej transakcji. Jeśli usunięcie zostanie zatwierdzone, kod wyzwalacza wykrywa naruszenie integralności i zgłasza oczekiwany błąd.
Identyfikowanie zapytań, które mogą nie działać poprawnie w ramach RCSI to nietrywialne zadanie, które może wymagać dogłębnych testów aby uzyskać właściwe rozwiązanie (i pamiętaj, że te problemy są dość ogólne i nie ograniczają się do kodu wyzwalającego!) Ponadto dodaj READCOMMITTEDLOCK
wskazówka do każdej tabeli, która tego potrzebuje, może być żmudnym i podatnym na błędy procesem. Dopóki SQL Server nie zapewni szerszej opcji żądania implementacji blokowania tam, gdzie jest to potrzebne, utknęliśmy z użyciem wskazówek dotyczących tabeli.
Następnym razem
Następny post z tej serii kontynuuje nasze badanie izolacji popełnionych migawek odczytu, przyglądając się zaskakującemu zachowaniu instrukcji modyfikacji danych w RCSI.
[ Zobacz indeks dla całej serii ]