Jak pisze Paweł:Nie, to nie jest bezpieczne , dla którego chciałbym dodać dowody empiryczne:Utwórz tabelę Table_1
z jednym polem ID
i jeden rekord o wartości 0
. Następnie wykonaj następujący kod jednocześnie w dwóch oknach zapytań 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
Następnie wykonaj
SELECT ID, COUNT(*) FROM Table_1 GROUP BY ID HAVING COUNT(*) > 1
Na moim serwerze SQL Server 2008 jeden identyfikator (662
) został utworzony dwukrotnie. Tak więc domyślny poziom izolacji stosowany do pojedynczych instrukcji to nie wystarczy.
EDYCJA:Oczywiście, zawijanie INSERT
z BEGIN TRANSACTION
i COMMIT
nie naprawi tego, ponieważ domyślny poziom izolacji dla transakcji to nadal READ COMMITTED
, co nie jest wystarczające. Pamiętaj, że ustawienie poziomu izolacji transakcji na REPEATABLE READ
jest również niewystarczający. Jedyny sposób na uczynienie powyższego kodu bezpiecznym jest dodać
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
na górze. To jednak od czasu do czasu powodowało impas w moich testach.
EDYCJA:Jedyne rozwiązanie, które znalazłem, które jest bezpieczne i nie powoduje zakleszczeń (przynajmniej w moich testach) jest jawne zablokowanie wyłącznie tabeli (domyślny poziom izolacji transakcji jest tutaj wystarczający). Uważaj jednak; to rozwiązanie może zabić wydajność:
...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...