TL; DR: Poniższe pytanie sprowadza się do: Podczas wstawiania wiersza istnieje okno możliwości między wygenerowaniem nowej Identitywartości a zablokowaniem odpowiedniego klucza wiersza w indeksie klastrowym, w którym zewnętrzny obserwator mógłby zobaczyć nowszą Identity wartość wprowadzona przez równoległą transakcję? (W SQL Server.)
Wersja szczegółowa
Mam tabelę programu SQL Server z Identitykolumną o nazwie CheckpointSequence, która jest kluczem indeksu klastrowanego tabeli (który ma również szereg dodatkowych indeksów nieklastrowanych). Wiersze są wstawiane do tabeli przez kilka równoległych procesów i wątków (na poziomie izolacji READ COMMITTEDi bez IDENTITY_INSERT). Jednocześnie istnieją procesy okresowo odczytujące wiersze z indeksu klastrowego, uporządkowane według tej CheckpointSequencekolumny (również na poziomie izolacji READ COMMITTED, z READ COMMITTED SNAPSHOTwyłączoną opcją).
Obecnie polegam na tym, że procesy odczytu nigdy nie mogą „pominąć” punktu kontrolnego. Moje pytanie brzmi: czy mogę polegać na tej właściwości? A jeśli nie, co mogę zrobić, aby było to prawdą?
Przykład: po wstawieniu wierszy o wartościach tożsamości 1, 2, 3, 4 i 5 czytelnik nie może zobaczyć wiersza o wartości 5 przed zobaczeniem wiersza o wartości 4. Testy pokazują, że zapytanie zawierające ORDER BY CheckpointSequenceklauzulę ( i aWHERE CheckpointSequence > -1 klauzula), niezawodnie blokuje za każdym razem, gdy wiersz 4 ma zostać odczytany, ale jeszcze nie zatwierdzony, nawet jeśli wiersz 5 został już zatwierdzony.
Uważam, że przynajmniej teoretycznie mogą istnieć warunki rasowe, które mogą spowodować załamanie tego założenia. Niestety, dokumentacja Identitynie mówi wiele o tym, jak Identitydziała w kontekście wielu równoczesnych transakcji, mówi tylko: „Każda nowa wartość jest generowana na podstawie bieżącego ziarna i przyrostu”. i „Każda nowa wartość dla określonej transakcji różni się od innych równoczesnych transakcji w tabeli”. ( MSDN )
Moje rozumowanie jest takie, że musi działać jakoś tak:
- Transakcja jest uruchamiana (jawnie lub niejawnie).
- Generowana jest wartość tożsamości (X).
- Odpowiednia blokada wiersza jest pobierana na indeks klastrowany na podstawie wartości tożsamości (chyba że uruchomi się eskalacja blokady, w którym to przypadku cała tabela jest blokowana).
- Wiersz został wstawiony.
- Transakcja zostaje zatwierdzona (być może sporo czasu później), więc blokada jest ponownie usuwana.
Myślę, że pomiędzy krokiem 2 a 3 jest bardzo małe okno, w którym
- równoczesna sesja może wygenerować następną wartość tożsamości (X + 1) i wykonać wszystkie pozostałe kroki,
- w ten sposób pozwalając czytelnikowi przyjść dokładnie w tym momencie, aby odczytać wartość X + 1, pomijając wartość X.
Oczywiście prawdopodobieństwo tego wydaje się bardzo niskie; ale nadal - może się zdarzyć. A może to?
(Jeśli interesuje Cię kontekst: jest to implementacja SQL Persistence Engine NEventStore. NEventStore implementuje magazyn zdarzeń tylko do dołączania, w którym każde zdarzenie otrzymuje nowy, rosnący numer sekwencji kontrolnej. Klienci odczytują zdarzenia z magazynu zdarzeń uporządkowane według punktu kontrolnego aby wykonać wszelkiego rodzaju obliczenia. Po przetworzeniu zdarzenia z punktem kontrolnym X klienci biorą pod uwagę tylko „nowsze” zdarzenia, tj. zdarzenia z punktem kontrolnym X + 1 i wyższym. Dlatego ważne jest, aby zdarzenia nigdy nie były pomijane, ponieważ nigdy więcej nie będą brane pod uwagę. Obecnie próbuję ustalić, czy Identityimplementacja punktu kontrolnego na podstawie tego warunku spełnia ten wymóg. Są to dokładnie używane instrukcje SQL : Schemat , zapytanie Writer ,Zapytanie czytelnika ).
Jeśli mam rację i może się zdarzyć sytuacja opisana powyżej, widzę tylko dwie opcje radzenia sobie z nimi, obie są niezadowalające:
- Gdy zobaczysz wartość sekwencji punktu kontrolnego X + 1 przed zobaczeniem X, odrzuć X + 1 i spróbuj ponownie później. Ponieważ jednak
Identitymoże oczywiście tworzyć luki (np. Przy wycofywaniu transakcji), X może nigdy nie nadejść. - To samo podejście, ale zaakceptuj przerwę po n milisekundach. Jaką wartość n należy jednak przyjąć?
Jakieś lepsze pomysły?
