web-dev-qa-db-fra.com

Dans SQL Server, comment puis-je verrouiller une seule ligne d'une manière similaire à "SELECT FOR UPDATE WAIT" d'Oracle?

J'ai un programme qui se connecte à une base de données Oracle et y effectue des opérations. Je souhaite maintenant adapter ce programme pour prendre également en charge une base de données SQL Server.

Dans la version Oracle, j'utilise "SELECT FOR UPDATE WAIT" pour verrouiller les lignes spécifiques dont j'ai besoin. Je l'utilise dans des situations où la mise à jour est basée sur le résultat du SELECT et les autres sessions ne peuvent absolument pas le modifier simultanément, donc ils doivent d'abord le verrouiller manuellement. Le système est fortement soumis à des sessions essayant d'accéder aux mêmes données en même temps.

Par exemple:
Deux utilisateurs tentent de récupérer la ligne de la base de données avec la priorité la plus élevée, la marquent comme occupée, y effectuent des opérations et la marquent à nouveau comme disponible pour une utilisation ultérieure. Dans Oracle, la logique se déroulerait essentiellement comme ceci:

BEGIN TRANSACTION;
SELECT ITEM_ID FROM TABLE_ITEM WHERE ITEM_PRIORITY > 10 AND ITEM_CATEGORY = 'CT1'
    ITEM_STATUS = 'available' AND ROWNUM = 1 FOR UPDATE WAIT 5;
UPDATE [locked item_id] SET ITEM_STATUS = 'unavailable';
COMMIT TRANSACTION;

Notez que les requêtes sont construites dynamiquement dans mon code. Notez également que lorsque la ligne précédemment la plus favorable est marquée comme indisponible, le deuxième utilisateur ira automatiquement pour la suivante et ainsi de suite. De plus, différents utilisateurs travaillant sur différentes catégories n'auront pas à attendre le déverrouillage des uns des autres. Le pire vient au pire, après 5 secondes, une erreur serait renvoyée et l'opération serait annulée.

Donc, finalement, la question est: comment puis-je obtenir les mêmes résultats dans SQL Server? J'ai étudié des indices de verrouillage qui, en théorie, semblent devoir fonctionner. Cependant, les seuls verrous qui empêchent d'autres verrous sont "UPDLOCK" ET "XLOCK" qui ne fonctionnent tous les deux qu'au niveau de la table.
. l'article correspondant).

Certaines personnes semblent ajouter une colonne "modifiée en temps" afin que les sessions puissent vérifier que ce sont bien elles qui l'ont modifiée, mais cela semble qu'il y aurait beaucoup d'accès redondants et inutiles.

21
Paradoxyde

Dans SQL Server, il existe des conseils de verrouillage, mais ils ne s'étendent pas sur leurs instructions comme l'exemple Oracle que vous avez fourni. La façon de le faire dans SQL Server consiste à définir un niveau d'isolement sur la transaction qui contient les instructions que vous souhaitez exécuter. Voir cette page MSDN mais la structure générale ressemblerait à quelque chose comme:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;

    select * from ...

    update ...

COMMIT TRANSACTION;

SERIALIZABLE est le niveau d'isolement le plus élevé. Voir le lien pour d'autres options. Depuis MSDN:

SERIALIZABLE Spécifie les éléments suivants:

Les relevés ne peuvent pas lire les données qui ont été modifiées mais pas encore validées par d'autres transactions.

Aucune autre transaction ne peut modifier les données qui ont été lues par la transaction en cours tant que la transaction en cours n'est pas terminée.

Les autres transactions ne peuvent pas insérer de nouvelles lignes avec des valeurs de clé qui tomberaient dans la plage de clés lues par les instructions de la transaction en cours jusqu'à ce que la transaction en cours se termine.

9
Paul Sasik

Vous recherchez probablementwith (updlock, holdlock). Cela fera un select saisir un verrou exclusive, qui est requis pour les mises à jour, au lieu d'un verrou shared. L'astuce holdlock indique à SQL Server de conserver le verrou jusqu'à la fin de la transaction.

FROM TABLE_ITEM with (updlock, holdlock)
16
Andomar

Comme documentation l'a dit:

XLOCK

Spécifie que des verrous exclusifs doivent être pris et maintenus jusqu'à la fin de la transaction. Si spécifié avec ROWLOCK, PAGLOCK ou TABLOCK, les verrous exclusifs s'appliquent au niveau de granularité approprié.

La solution utilise donc WITH(XLOCK, ROWLOCK):

BEGIN TRANSACTION;

SELECT ITEM_ID
FROM TABLE_ITEM
WITH(XLOCK, ROWLOCK)
WHERE ITEM_PRIORITY > 10 AND ITEM_CATEGORY = 'CT1' AND ITEM_STATUS = 'available' AND ROWNUM = 1;

UPDATE [locked item_id] SET ITEM_STATUS = 'unavailable';

COMMIT TRANSACTION;
9
Guido Mocha

Avez-vous essayé AVEC (ROWLOCK)?

BEGIN TRAN

   UPDATE your_table WITH (ROWLOCK)
   SET your_field = a_value
   WHERE <a predicate>

COMMIT TRAN
1
Pablushka