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 я должен принять?
Есть идеи получше?
