2009-06-03 4 views
5

Je souhaite implémenter des balises SO like sur userpost. J'ai une table appelée tag_data avec les colonnes tagId, title, count. J'ai une table séparée qui relie la relation entre un poste et les nombreux tags qu'il peut utiliser.Mettre à jour un compte (champ) en toute sécurité dans SQL

Voici le problème, comment puis-je compter le courant, l'augmenter ou le diminuer de un et le stocker en toute sécurité. Donc, aucune autre connexion/thread ne le mettra à jour entre le moment que je sélectionne et la mise à jour?

+0

Avez-vous besoin de protéger également l'insertion d'une nouvelle étiquette? Cela complique généralement les choses * assez * un peu. –

+0

mise à jour tag_data set count = count + @incrVal où tagId = @tagId – spencer7593

Répondre

13

Je suppose que vous voulez aussi le nouveau compte, sinon c'est une évidence, il suffit de mettre à jour set count = count + 1.

Si votre soutien db clause de sortie sur UPDATE (par exemple 2K5 SQL Server ou 2K8.):

UPDATE table 
    SET count = count + 1 
    OUTPUT inserted.count 
    WHERE [email protected]; 

autrement:

begin transaction 
update table 
    set counter=counter+1 
    where [email protected]; 
select counter 
    from table 
    where [email protected]; 
commit; 
+2

Bonne réponse. Je suis confus à propos de deux choses. Il semble que update/select soit une déclaration, alors pourquoi avez-vous besoin d'une transaction? La seconde est à en juger par l'autre réponse 2, il semble que la transaction va verrouiller la base de données jusqu'à ce qu'elle soit terminée, alors pourquoi MikeyB réponse est mauvaise? J'ai lu votre réponse mais que se passe-t-il ?, d'autres threads peuvent lire mais ne pas écrire pendant la transaction? –

+3

Le verrou X acquis par la mise à jour est utile * pour la durée de la transaction * et empêche quiconque de lire ou de mettre à jour le compteur jusqu'à ce que vous validiez. Cela vous donne un endroit sûr pour faire votre choix en sachant que vous récupérerez la valeur réelle que vous avez mise à jour. Dans le cas de Mikey, le verrou S acquis par le select est * not * help une fois l'instruction terminée (dans des conditions normales), permettant ainsi à un second thread de se faufiler et de mettre à jour la valeur au moment de l'exécution de la mise à jour. remplacer cette mise à jour. D'où la «mise à jour perdue». –

+0

bonne réponse. @Remus: qu'en est-il de régler le niveau d'isolation de Transaction sur 'serializable'? –

-2

pseudocode:

begin transaction 
A = select count from tag_data where tagId = TagId 
update tag_data set count = A+1 where tagId = TagId 
commit 
end transaction 

Je recommande fortement faire une procédure stockée appelée, par exemple, increment_tag(TagId) qui fait ce qui précède :)

+1

Rien n'empêche un thread simultané de lire le même nombre, de l'incrémenter et de le mettre à jour. C'est l'exemple canonique du cas de mise à jour perdue dans le traitement des transactions. Vous devez protéger la sélection initiale avec au moins un niveau d'isolement en lecture répétée (ou un holdlock). –

+0

cette solution (très mauvaise) peut également être corrigée en changeant l'instruction SELECT dans SELECT ... FOR UPDATE. Cela entraînera l'obtention d'un verrouillage sur cette ligne. Ce verrou contiendra d'autres transactions qui veulent effectuer une mise à jour/suppression ou une autre sélection pour mise à jour sur cette ligne, jusqu'à ce que cette transaction valide/annule. Rappelez-vous que vous devez vous assurer que chaque morceau de code qui modifie ce compteur utilise aussi select pour la mise à jour ou utilise la règle "mise à jour puis sélection", sinon tout cela est inutile. –

0

SET Count = count + 1 est à mon humble avis la solution la plus simple ..

Plus généralement le concept d'être en mesure d'obtenir des données, traiter et alors que sa demande qu'il y ait traité aucun changement sous-jacents avant d'écrire les résultats de la Le traitement n'est habituellement pas raisonnable si vous avez également besoin d'un système évolutif. Vous pouvez bien sûr le faire et, dans de nombreux environnements, vous y parviendrez. Cependant, ces approches mettront des limites sévères à la visibilité et à la complexité d'une application avant que les problèmes de simultanéité rendent le système inutilisable. À mon humble avis, la meilleure approche consiste à prendre une route optimiste et à détecter/réessayer si, dans le cas d'un cas inhabituel, quelque chose qui vous intéresse change.

SELECT COUNT AS vieux ... DE ...

.. traitement ...

UPDATE ... SET Count = oldplus1 où le comte = vieux et ...

À moins UPDATE vous donne le nombre de lignes que vous attendez que vous supposiez que les données ont été modifiées et réessayez jusqu'à ce qu'il réussisse.