TL; DR: Питання нижче зводиться до: Під час вставлення рядка чи існує вікно можливостей між генерацією нового Identity
значення та блокуванням відповідного ключа рядка в кластерному індексі, де зовнішній спостерігач міг бачити новіший Identity
значення, вставлене одночасною транзакцією? (У SQL Server.)
Детальна версія
У мене є таблиця SQL Server із Identity
стовпчиком під назвою CheckpointSequence
, який є ключем кластерного індексу таблиці (який також має ряд додаткових некластеризованих індексів). Рядки вставляються в таблицю декількома паралельними процесами та потоками (на рівні ізоляції READ COMMITTED
та без IDENTITY_INSERT
). У той же час, періодично читаються рядки з кластерного індексу, упорядковані цим CheckpointSequence
стовпцем (також на рівні ізоляції READ COMMITTED
, при цьому READ COMMITTED SNAPSHOT
параметр вимкнено).
Зараз я покладаюся на те, що процеси читання ніколи не можуть "пропустити" контрольну точку. Моє запитання: чи можна покластися на цю власність? А якщо ні, то що я можу зробити, щоб це було правдою?
Приклад: Коли вставляються рядки зі значеннями ідентичності 1, 2, 3, 4 і 5, читач не повинен бачити рядок зі значенням 5 перед тим, як побачити той, який має значення 4. Тести показують, що запит, який містить ORDER BY CheckpointSequence
пункт ( і WHERE CheckpointSequence > -1
пункт) надійно блокує щоразу, коли рядок 4 має бути прочитаний, але ще не скоєний, навіть якщо рядок 5 вже був скоєний.
Я вважаю, що принаймні теоретично тут може бути умова перегонів, яка може призвести до порушення цього припущення. На жаль, документація про Identity
багато не говорить про те, як Identity
працює в умовах декількох одночасних транзакцій, вона лише говорить "Кожне нове значення генерується на основі поточного насіння та приросту". і "Кожне нове значення для певної транзакції відрізняється від інших одночасних транзакцій у таблиці." ( MSDN )
Моє міркування таке: воно повинне діяти якось так:
- Починається транзакція (явно або неявно).
- Створюється значення ідентичності (X).
- Відповідне блокування рядків береться за кластерним індексом на основі значення ідентичності (якщо не починається ескалація блокування, в цьому випадку вся таблиця заблокована).
- Рядок вставляється.
- Угода здійснена (можливо, досить багато часу пізніше), тому замок знову знімається.
Я думаю, що між кроком 2 і 3 є дуже крихітне вікно, куди
- одночасний сеанс може генерувати наступне значення ідентичності (X + 1) та виконати всі інші кроки,
- таким чином, дозволяючи читачеві, що приходить саме в цей момент часу, прочитати значення X + 1, пропустивши значення X.
Звичайно, ймовірність цього здається надзвичайно низькою; але все ж - це могло статися. Або могло?
(Якщо вас зацікавив контекст: це реалізація SQL Persistent Engine NEventStore. NEventStore реалізує сховище лише для додатків, де кожна подія отримує новий порядковий номер контрольно-пропускного коду, що збільшується. Клієнти читають події з магазину подій, замовленого контрольною точкою для виконання будь-яких обчислень. Після того, як подія з контрольною точкою X буде оброблена, клієнти розглядають лише "новіші" події, тобто події з контрольною точкою X + 1 і вище. Тому важливо, щоб події ніколи не можна було пропустити, тому що вони ніколи не будуть розглянуті знову. Зараз я намагаюся визначити, чи Identity
реалізація контрольно-пропускної бази відповідає цій вимозі. Це саме використовувані оператори SQL : схема , запит письменника ,Запит читача .)
Якщо я маю рацію і може скластись описана вище ситуація, я бачу лише два варіанти боротьби з ними, обидва з них незадовільні:
- Побачивши значення послідовності контрольної точки X + 1 перед тим, як побачити X, відхиліть X + 1 і повторіть спробу пізніше. Однак, оскільки,
Identity
звичайно, може виникнути прогалини (наприклад, коли транзакція відкочується назад), X ніколи не може відбутися. - Отже, такий же підхід, але прийняти проміжок через п ять мілісекунд. Однак яке значення n слід припустити?
Якісь кращі ідеї?