Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Dans tsql, une insertion avec une instruction Select est-elle sûre en termes de concurrence ?

Comme l'écrit Paul :Non, ce n'est pas sûr , pour laquelle je voudrais ajouter des preuves empiriques :Créer une table Table_1 avec un champ ID et un enregistrement avec la valeur 0 . Exécutez ensuite le code suivant simultanément dans deux fenêtres de requête 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

Puis exécutez

SELECT ID, COUNT(*) FROM Table_1 GROUP BY ID HAVING COUNT(*) > 1

Sur mon SQL Server 2008, un ID (662 ) a été créé deux fois. Ainsi, le niveau d'isolement par défaut appliqué aux instructions uniques est not suffisant.

EDIT :clairement, enveloppant le INSERT avec BEGIN TRANSACTION et COMMIT ne le résoudra pas, car le niveau d'isolement par défaut pour les transactions est toujours READ COMMITTED , ce qui n'est pas suffisant. Notez que définir le niveau d'isolement de la transaction sur REPEATABLE READ est aussi insuffisant. La seule façon de rendre le code ci-dessus sûr est d'ajouter

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

au sommet. Cependant, cela provoquait des blocages de temps en temps lors de mes tests.

EDIT :La seule solution que j'ai trouvée qui est sûre et ne produit pas de blocages (du moins dans mes tests) est de verrouiller explicitement la table exclusivement (le niveau d'isolement de la transaction par défaut est suffisant ici). Méfiez-vous cependant; cette solution pourrait tuer performances :

...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...