Read Committed Snapshot имеет дело только с блокировками при выборе данных из таблиц.
Однако в t1 и t2 вы ОБНОВЛЯЕТЕ данные, что является другим сценарием.
Когда вы ОБНОВЛЯЕТЕ счетчик, вы переходите в блокировку записи (в строке), предотвращая появление другого обновления. t2 может читать, но t2 будет блокировать его ОБНОВЛЕНИЕ до тех пор, пока не будет выполнено t1, и t2 не сможет зафиксировать до t1 (что противоречит вашей временной шкале). Только одна из транзакций получит обновление счетчика, поэтому обе будут корректно обновлять счетчик с учетом представленного кода. (Проверено)
- counter = 0
- Счетчик обновлений t1 (counter => 1)
- Счетчик обновлений t2 (заблокирован)
- t1 commit (counter = 1)
- t2 разблокирован (теперь можно обновить счетчик) (counter => 2)
- t2 commit
Read Committed означает, что вы можете только читать зафиксированные значения, но это не означает, что у вас есть Repeatable Reads. Таким образом, если вы используете переменную counter и зависите от нее и намереваетесь обновить ее позже, возможно, вы выполняете транзакции с неверным уровнем изоляции.
Вы можете использовать повторяющуюся блокировку чтения или, если вы только иногда обновляете счетчик, вы можете сделать это самостоятельно, используя метод оптимистичной блокировки. например столбец отметки времени с таблицей счетчиков или условное обновление.
DECLARE @CounterInitialValue INT
DECLARE @NewCounterValue INT
SELECT @CounterInitialValue = SELECT counter FROM MyTable WHERE MyID = 1234
-- do stuff with the counter value
UPDATE MyTable
SET counter = counter + 1
WHERE
MyID = 1234
AND
counter = @CounterInitialValue -- prevents the update if counter changed.
-- the value of counter must not change in this scenario.
-- so we rollback if the update affected no rows
IF( @@ROWCOUNT = 0 )
ROLLBACK
Эта статья devx является информативной, хотя в ней говорится о функциях, пока они еще были в бета-версии, поэтому она может быть не совсем точной.
обновление: как показывает Справедливость, если t2 является вложенной транзакцией в t1, семантика различна. Опять же, оба будут корректно обновлять счетчик (+2), потому что с точки зрения t2 внутри t1 счетчик уже обновлялся один раз. Вложенный t2 не имеет доступа к тому счетчику, который был до обновления t1.
- counter = 0
- Счетчик обновлений t1 (counter => 1)
- Счетчик обновлений t2 (вложенная транзакция) (counter => 2)
- t2 commit
- t1 commit (counter = 2)
В случае вложенной транзакции, если t1 выдает ROLLBACK после t1 COMMIT, счетчик возвращается к своему первоначальному значению, поскольку он также отменяет фиксацию t2.