2010-01-21 2 views
4

Dire que j'ai le tableau suivant:SELECT et UPDATE table donc il n'y a pas de chevauchement de threads

ID|Read 
------- 
1|true 
2|false 
3|false 
4|false 

... et je dois lire l'ID le plus petit, qui a [Lire] == false; plus, mise à jour que je l'ai maintenant lu. Donc si j'exécute mon dbo.getMinID de procédure stockée, il renverra l'ID: 2, et mettra à jour [Read] -> true.

CREATE PROCEDURE [dbo].[getMinID] 
(
    @QueryID INT OUTPUT 
) 
BEGIN 
    SELECT TOP 1 @QueryID = [ID] from Table 
    UPDATE Table SET [Read] = 1 WHERE [ID] = @QueryID 
END 

Le problème est que je dix (10) Fils asynchrones exécuter dbo.getMinID, en même temps, et je ne peux pas les sélectionner le même [ID] en aucun cas. Je suis inquiet qu'un second thread s'exécute entre mes instructions SELECT et UPDATE, retournant [ID]: 2 dans les deux scénarios.

Comment puis-je m'assurer de ne pas sélectionner/mettre à jour le même enregistrement deux fois, quel que soit le nombre de threads agissant sur la procédure stockée? AUSSI, gardez à l'esprit que la table a CONSTANTLY de nouvelles lignes ajoutées, donc je ne peux pas verrouiller la table!

Répondre

3

Si vous voulez dire un verrouillage de type de file d'attente sécurisé, alors utilisez les indicateurs ROWLOCK, UPDLOCK, READPAST?

SQL Server Process Queue Race Condition

BEGIN TRAN 

SELECT TOP 1 @QueryID = [ID] from Table WITH (ROWLOCK, UPDLOCK, READPAST) 
UPDATE Table SET [Read] = 1 WHERE [ID] = @QueryID 

COMMIT TRAN -- TRAM 

Cependant, dans une déclaration. Quelque chose comme

WITH T AS 
(
    --ORDER BY with TOP , or perhaps MIN is better? 
    SELECT TOP 1 [Read], [ID] from Table 
    WITH (ROWLOCK, UPDLOCK, READPAST) ORDER BY [Read] 
) 
UPDATE 
    T 
SET 
    [Read] = 1; 
+0

sera min, ne pas déclencher un verrou sur toutes les lignes? ou est-ce que je dis juste un non-sens –

+0

verrou partagé qui est en lecture seule, et il passera d'autres verrous à cause de READPAST. Peu importe, vraiment ... – gbn

0

mettre les instructions select et update et select dans une transaction et au début de la transaction verrouiller la table afin que les threads sortants attendent. Cordialement, Iordan

+0

Je ne veux pas verrouiller la table parce que les INSERT sont constamment effectués.plus demain, il peut y avoir 1000 Threads en cours d'exécution sur la même table, ce qui signifie que j'ai besoin d'un moyen moins intrusif, mais explicite de le faire. –

+1

@Theofanis - ce n'est pas grave * comment * rapide votre accès db est si vous faites un gâchis des données; d'abord essayer de la manière la plus simple (avec une transaction), * puis * défier la performance. –

+1

Si c'est le cas et que le nombre de threads peut aller jusqu'à 1000, essayez d'ajouter une nouvelle colonne null dans la table. la première chose que la bande de roulement fera est d'écrire son identifiant dans la nouvelle colonne afin qu'aucun autre thread ne puisse prendre cette ligne. – IordanTanev

1

Si vous voulez qu'il soit atomique, vous devez verrouiller quelque chose, mais cela ne signifie pas que vous devez verrouiller longtemps. Je voudrais tout d'abord essayer avec certaines transactions scope bien, mais je serais également intéressé à essayer la variante de mise à jour qui fait un SELECT en même temps:

UPDATE TOP (1) [foo] 
SET [read] = 1 
OUTPUT INSERTED.id 
WHERE [read] = 0 

Vous pouvez voir si cela a des problèmes de concurrence - En toute honnêteté, je ne sais pas sans vérifier! Vous devrez peut-être ajouter quelque chose comme WITH (ROWLOCK). Personnellement, cependant, je voudrais rester simple et essayer une transaction sérialisable.

Notez également que cela ne garantit pas qui enregistrement que vous obtiendrez

+0

Cela ne me dérange pas de verrouiller le ROW, aussi longtemps que je ne verrouille pas la table. –

+0

Vous voulez définir le niveau d'isolement de la transaction sur sérialisable? –

+0

Si vous définissez le niveau de transaction sur sérialisable et utilisez une transaction, vous devriez pouvoir utiliser votre code * original *. –

1

Faites votre niveau d'isolation des transactions SERIALIZABLE et placez un verrou exclusif avec votre commande SELECT (premier dernier?):

SELECT TOP 1 @QueryID = [ID] from Table WITH (XLOCK) ORDER BY id DESC 
UPDATE Table SET [Read] = 1 WHERE [ID] = @QueryID 

Cela placera un XLOCK sur la plage de touches supérieure et empêchera les requêtes simultanées de lire l'enregistrement supérieur.

De cette façon, aucune transaction n'obtiendra jamais le même enregistrement.