Como escribe Paul:No, no es seguro , para lo cual me gustaría agregar evidencia empírica:Crear una tabla Table_1
con un campo ID
y un registro con valor 0
. Luego ejecute el siguiente código simultáneamente en dos ventanas de consulta de 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
Luego ejecuta
SELECT ID, COUNT(*) FROM Table_1 GROUP BY ID HAVING COUNT(*) > 1
En mi SQL Server 2008, una ID (662
) se creó dos veces. Por lo tanto, el nivel de aislamiento predeterminado que se aplica a declaraciones individuales es no suficiente.
EDITAR:Claramente, envolviendo el INSERT
con BEGIN TRANSACTION
y COMMIT
no lo arreglará, ya que el nivel de aislamiento predeterminado para las transacciones sigue siendo READ COMMITTED
, que no es suficiente. Tenga en cuenta que establecer el nivel de aislamiento de transacciones en REPEATABLE READ
es también insuficiente. La única forma de hacer que el código anterior sea seguro es agregar
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
en la cima. Sin embargo, esto provocó bloqueos de vez en cuando en mis pruebas.
EDITAR:La única solución que encontré que es segura y no produce interbloqueos (al menos en mis pruebas) es bloquear explícitamente la tabla exclusivamente (el nivel de aislamiento de transacción predeterminado es suficiente aquí). Sin embargo, ten cuidado; esta solución podría matar rendimiento:
...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...