Si vous ne maintenez pas de table de comptoir, il y a deux options. Dans une transaction, sélectionnez d'abord le MAX(seq_id)
avec l'un des indicateurs de table suivants :
WITH(TABLOCKX, HOLDLOCK)
WITH(ROWLOCK, XLOCK, HOLDLOCK)
TABLOCKX + HOLDLOCK
est un peu exagéré. Il bloque les instructions de sélection régulières, qui peuvent être considérées comme lourdes même si la transaction est petite.
A ROWLOCK, XLOCK, HOLDLOCK
l'indice de table est probablement une meilleure idée (mais :lisez l'alternative avec une table de compteur plus loin). L'avantage est qu'il ne bloque pas les instructions de sélection régulières, c'est-à-dire lorsque les instructions de sélection n'apparaissent pas dans un SERIALIZABLE
transaction, ou lorsque les instructions select ne fournissent pas les mêmes indicateurs de table. Utilisation de ROWLOCK, XLOCK, HOLDLOCK
bloquera toujours les instructions d'insertion.
Bien sûr, vous devez être sûr qu'aucune autre partie de votre programme ne sélectionne le MAX(seq_id)
sans ces indications de table (ou en dehors d'un SERIALIZABLE
transaction) puis utilisez cette valeur pour insérer des lignes.
Notez qu'en fonction du nombre de lignes verrouillées de cette manière, il est possible que SQL Server transforme le verrou en verrou de table. En savoir plus sur l'escalade de verrouillage ici .
La procédure d'insertion utilisant WITH(ROWLOCK, XLOCK, HOLDLOCK)
ressemblerait à ceci :
DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Spine';
BEGIN TRY
BEGIN TRANSACTION;
DECLARE @max_seq INT=(SELECT MAX(seq) FROM dbo.table_seq WITH(ROWLOCK,XLOCK,HOLDLOCK) WHERE [email protected]_model);
IF @max_seq IS NULL SET @max_seq=0;
INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@max_seq+1,@target_model);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
Une alternative et probablement une meilleure idée est d'avoir un compteur table, et fournissez ces conseils de table sur la table de comptoir. Ce tableau ressemblerait à ceci :
CREATE TABLE dbo.counter_seq(model INT PRIMARY KEY, seq_id INT);
Vous modifieriez alors la procédure d'insertion comme suit :
DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Spine';
BEGIN TRY
BEGIN TRANSACTION;
DECLARE @new_seq INT=(SELECT seq FROM dbo.counter_seq WITH(ROWLOCK,XLOCK,HOLDLOCK) WHERE [email protected]_model);
IF @new_seq IS NULL
BEGIN SET @new_seq=1; INSERT INTO dbo.counter_seq(model,seq)VALUES(@target_model,@new_seq); END
ELSE
BEGIN SET @new_seq+=1; UPDATE dbo.counter_seq SET [email protected]_seq WHERE [email protected]_model; END
INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@new_seq,@target_model);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
L'avantage est que moins de verrous de ligne sont utilisés (c'est-à-dire un par modèle dans dbo.counter_seq
), et l'escalade de verrous ne peut pas verrouiller l'ensemble de dbo.table_seq
table bloquant ainsi les instructions select.
Vous pouvez tester tout cela et constater vous-même les effets, en plaçant un WAITFOR DELAY '00:01:00'
après avoir sélectionné la séquence de counter_seq
, et jongler avec le(s) tableau(x) dans un deuxième onglet SSMS.
PS1 :Utilisation de ROW_NUMBER() OVER (PARTITION BY model ORDER BY ID)
n'est pas un bon moyen. Si des lignes sont supprimées/ajoutées ou si les identifiants sont modifiés, la séquence changera (considérez les identifiants de facture qui ne devraient jamais changer). Également en termes de performances, devoir déterminer les numéros de ligne de toutes les lignes précédentes lors de la récupération d'une seule ligne est une mauvaise idée.
PS2 :Je n'utiliserais jamais de ressources externes pour assurer le verrouillage, alors que SQL Server fournit déjà un verrouillage via des niveaux d'isolement ou des indications de table précises.