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 от NEventStore . NEventStore реализует хранилище событий только для добавления, где каждое событие получает новый порядковый номер восходящей контрольной точки. Клиенты читают события из хранилища событий, упорядоченного по контрольной точке для выполнения всех видов вычислений. После обработки события с контрольной точкой X клиенты рассматривают только «более новые» события, т. е. события с контрольной точкой X + 1 и выше. Поэтому крайне важно, чтобы события никогда не пропускались, поскольку они никогда не будут рассматриваться снова. В настоящее время я пытаюсь определить, соответствует ли Identity
реализация контрольной точки на основе этого требования. Это именно те операторы SQL, которые используются : Схема , запрос Writer ,Запрос читателя .)
Если я прав и описанная выше ситуация может возникнуть, я вижу только два варианта решения этих проблем, оба из которых неудовлетворительны:
- Если вы видите значение последовательности контрольных точек X + 1 до того, как увидите X, отклоните X + 1 и повторите попытку позже. Однако, поскольку,
Identity
конечно , могут возникать пробелы (например, при откате транзакции), X может никогда не прийти. - Таким образом, тот же подход, но принять разрыв через n миллисекунд. Тем не менее, какое значение n я должен принять?
Есть идеи получше?