Както пише Павел:Не, не е безопасно , за което бих искал да добавя емпирични доказателства:Създайте таблица Table_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...