Допустим, у вас есть следующий код (пожалуйста, не обращайте внимания, что это ужасно):
BEGIN TRAN;
DECLARE @id int
SELECT @id = id + 1 FROM TableA;
UPDATE TableA SET id = @id; --TableA must have only one row, apparently!
COMMIT TRAN;
-- @id is returned to the client or used somewhere else
На мой взгляд, это НЕ управляет параллелизмом должным образом. Тот факт, что у вас есть транзакция, не означает, что кто-то другой не будет читать то же значение, что и вы, прежде чем вы попадете в оператор обновления.
Теперь оставьте код как есть (я понимаю, что его лучше обрабатывать как один оператор или даже лучше, используя столбец автоинкремента / идентификатора), что является верным способом заставить его обрабатывать параллелизм правильно и предотвратить гонки, которые позволяют двум клиентам получить одинаковые значение идентификатора?
Я уверен, что добавление WITH (UPDLOCK, HOLDLOCK)
в SELECT поможет. Уровень изоляции транзакции SERIALIZABLE , по-видимому, также работает, поскольку он запрещает кому-либо читать то, что вы делали, до тех пор, пока не закончится транс ( ОБНОВЛЕНИЕ : это неверно. См. Ответ Мартина). Это правда? Будут ли они оба работать одинаково хорошо? Один предпочтительнее другого?
Представьте, что вы делаете что-то более законное, чем обновление идентификатора - некоторые вычисления основаны на чтении, которое вам нужно обновить. Там может быть много таблиц, некоторые из которых вы будете писать, а другие нет. Какова лучшая практика здесь?
Написав этот вопрос, я думаю, что подсказки блокировки лучше, потому что тогда вы блокируете только те таблицы, которые вам нужны, но я был бы признателен за чей-либо вклад.
PS И нет, я не знаю лучшего ответа и действительно хочу получить лучшее понимание! :)
update
что может быть основано на устаревших данных? В последнем случае вы можете использоватьrowversion
столбец, чтобы проверить, не была ли изменена строка, подлежащая обновлению, с момента ее чтения.Ответы:
Просто обращаясь к
SERIALIZABLE
аспекту уровня изоляции. Да, это будет работать, но с риском тупика.Две транзакции смогут одновременно читать строку. Они не будут блокировать друг друга, так как они будут либо брать объектную
S
блокировку, либо индекснуюRangeS-S
блокировку, зависящую от структуры таблицы, и эти блокировки совместимы . Но они будут блокировать друг друга при попытке получить блокировки, необходимые для обновления (IX
блокировка объекта или индексRangeS-U
соответственно), что приведет к взаимоблокировке.UPDLOCK
Вместо этого использование явной подсказки будет сериализовать чтения, что позволит избежать риска взаимоблокировки.источник
IX
вX
на самой куче. Интересно, что ни одна строка не подходит, поэтому никакие блокировки строк никогда не снимаются. Не уверен, почему он беретX
блокировку вообще.Я думаю, что лучшим подходом для вас было бы на самом деле выставить свой модуль на высокий уровень параллелизма и убедиться в этом самим. Иногда достаточно UPDLOCK, и нет необходимости в HOLDLOCK. Иногда sp_getapplock работает очень хорошо. Я бы не стал делать здесь никакого общего заявления - иногда добавление еще одного индекса, триггера или индексированного представления меняет результат. Нам нужно стресс-тестировать код и убедиться в этом на индивидуальной основе.
Я написал несколько примеров стресс-тестирования здесь
Изменить: для лучшего знания внутренних органов, вы можете прочитать книги Калена Делани. Однако книги могут быть не синхронизированы, как и любая другая документация. Кроме того, есть слишком много комбинаций, чтобы рассмотреть: шесть уровней изоляции, много типов блокировок, кластеризованные / некластеризованные индексы и кто знает, что еще. Это много комбинаций. Кроме того, SQL Server является закрытым исходным кодом, поэтому мы не можем загружать исходный код, отлаживать его и тому подобное - это будет основным источником знаний. Все остальное может быть неполным или устаревшим после следующего выпуска или пакета обновления.
Таким образом, вы не должны решать, что работает для вашей системы без собственного стресс-тестирования. Что бы вы ни читали, это может помочь вам понять, что происходит, но вы должны доказать, что прочитанный вами совет работает для вас. Я не думаю, что кто-то может сделать это для вас.
источник
В этом конкретном случае добавление
UPDLOCK
блокировки кSELECT
действительно предотвратит аномалии. ДобавлениеHOLDLOCK
не требуется, поскольку блокировка обновления удерживается на время транзакции, но я признаюсь, что сам включал ее как (возможно, плохую) привычку в прошлом.Нет лучшей практики. Ваш выбор управления параллелизмом должен быть основан на требованиях приложения. Некоторые приложения / транзакции должны выполняться так, как если бы они имели исключительное право собственности на базу данных, избегая аномалий и неточностей любой ценой. Другие приложения / транзакции могут терпеть некоторую степень помех друг от друга.
Редактировать: @ комментарий Алекса Кузнецова побудил меня перечитать вопрос и убрать очевидную ошибку в моем ответе. Обратите внимание на себя в конце ночного размещения.
источник