В моем ответе на это ТАК вопрос я предлагаю использовать единственный оператор вставки с выбором, который увеличивает значение, как показано ниже.
Insert Into VersionTable
(Id, VersionNumber, Title, Description, ...)
Select @ObjectId, max(VersionNumber) + 1, @Title, @Description
From VersionTable
Where Id = @ObjectId
Я предложил это, потому что я полагаю, что этот оператор безопасен с точки зрения параллелизма, в том, что, если другие вставляют для того же идентификатора объекта, выполняется одновременно, нет никакого шанса наличия дублирующихся номеров версий.
Я корректен?
Изоляция считывания по умолчанию делает это небезопасным, если два из них выполняются на идеальном паралеле, вы получите дубликат, так как нет примененной блокировки чтения.
Вам нужны уровни изоляции REPEATABLE READ или SERIALIZABLE, чтобы сделать это безопасным.
.Я думаю, вы предполагаете, что это неправильно. Когда вы запрашиваете таблицу VersionNumber, вы только ставите на строку блокировку чтения. Это не мешает другим пользователям читать одну и ту же строку из той же таблицы. Следовательно, два процесса могут одновременно читать одну и ту же строку в таблице VersionNumber и генерировать одно и то же значение VersionNumber.
.Вам нужно уникальное ограничение на (Id, VersionNumber) для его применения
Я бы использовал ROWLOCK, XLOCK намеки для блокировки других людей, читающих заблокированную строку, в которой вы вычисляется
или обертывания INSERT в TRY/CATCH. Если я получу дубликат, попробуйте еще раз...
Как пишет Пол: Нет, это небезопасно , для чего я хотел бы добавить эмпирическое свидетельство: Создайте таблицу Таблица_1
с одним полем ID
и одной записью со значением 0
. Затем выполните следующий код одновременно в двух окнах запросов Management Studio :
declare @counter int
set @counter = 0
while @counter < 1000
begin
set @counter = @counter + 1
INSERT INTO Table_1
SELECT MAX(ID) + 1 FROM Table_1
end
Затем выполните
SELECT ID, COUNT(*) FROM Table_1 GROUP BY ID HAVING COUNT(*) > 1
На моем SQL Server 2008 один идентификатор ( 662
) был создан дважды. Таким образом, уровень изоляции по умолчанию, применяемый к отдельным операторам, недостаточен .
РЕДАКТИРОВАТЬ: Очевидно, что упаковка INSERT
с BEGIN TRANSACTION
и COMMIT
не исправит этого, поскольку уровень изоляции по умолчанию для транзакций по-прежнему READ COMMITTED
, чего недостаточно. Обратите внимание, что установка уровня изоляции транзакции на REPEATABLE READ
является также недостаточной. Единственный способ сделать приведенный выше код безопасным - это добавить
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
вверху. Однако это время от времени приводило к тупикам в моих тестах.
РЕДАКТИРОВАТЬ: Единственное безопасное решение, которое я нашел и , не вызывает взаимоблокировок (по крайней мере, в моих тестах), - это явная блокировка таблицы исключительно (здесь достаточно уровня изоляции транзакции по умолчанию). Однако будьте осторожны; это решение может убить производительность:
...loop stuff...
BEGIN TRANSACTION
SELECT * FROM Table_1 WITH (TABLOCKX, HOLDLOCK) WHERE 1=0
INSERT INTO Table_1
SELECT MAX(ID) + 1 FROM Table_1
COMMIT
...loop end...