web-dev-qa-db-fra.com

Oracle Sélectionnez un comportement de mise à jour

Le problème que nous essayons de résoudre se présente comme suit.

  • Nous avons une table pleine de lignes qui représentent les cartes. Le but de l'opération de réservation est d'attribuer une carte à un client
  • Une carte ne peut pas appartenir à de nombreux clients
  • Après un certain temps (si ce n'est pas acheté) une carte doit être retourné à la piscine de resurces disponibles
  • La réservation peut être effectuée par de nombreux clients en même temps
  • Nous utilisons la base de données Oracle pour stocker les données, donc la solution doit travailler au moins sur Oracle 11

Notre solution est d'attribuer un statut à la carte, et le stocker de la date de réservation. En réservant une carte que nous faisons à l'aide de la déclaration "sélectionnez pour la mise à jour ". La requête recherche des cartes disponibles et pour les cartes qui ont été réservés depuis longtemps.

Cependant, notre requête ne fonctionne pas comme prévu.

J'ai préparé une situation simplifiée pour expliquer le problème. Nous avons une table card_numbers, plein de données - toutes les lignes ont des numéros d'identification non nuls. Maintenant, nous allons essayer de verrouiller certains d'entre eux.

-- first, in session 1
set autocommit off;

select id from card_numbers  
where id is not null  
and rownum <= 1  
for update skip locked;

Nous ne commettons pas la transaction ici, la ligne doit être verrouillée.

-- later, in session 2
set autocommit off;

select id from card_numbers  
where id is not null  
and rownum <= 1  
for update skip locked;

Le comportement attendu est que dans les deux séances, nous obtenons une seule autre ligne qui satisfait les conditions de.

Toutefois, il ne fonctionne pas de cette façon. Selon que nous utilisons le " Cheminement verrouillé " partie de la requête ou non - les changements behavious:

  • sans " Cheminement verrouillé " - deuxième session est bloquée - en attente de transaction validée ou annulée en session une
  • avec " saut verrouillé " - deuxième requête renvoie immédiatement ensemble de résultats vide

Ainsi, après cette longue introduction est la question.

Est-ce le genre de comportement de verrouillage souhaité possible dans Oracle? Si oui, qu'est-ce qu'on fait de mal? Quelle serait la bonne solution?

18
mateusz.fiolka

Le comportement que vous avez rencontré pour la mise à jour Skip verrouillé a été décrit dans ce blog Note . Ma compréhension est que la clause de mise à jour est évaluée après la clause WHERE. Le skip verrouillé est comme un filtre supplémentaire qui garantit que parmi les rangées qui auraient été renvoyées, aucune n'est verrouillée.

Votre déclaration est logiquement équivalente à: trouvez la première ligne de card_numbers et le retourner s'il n'est pas verrouillé. Évidemment, ce n'est pas ce que vous voulez.

Voici un petit cas de test qui reproduit le comportement que vous décrivez:

SQL> CREATE TABLE t (ID PRIMARY KEY)
  2  AS SELECT ROWNUM FROM dual CONNECT BY LEVEL <= 1000;

Table created

SESSION1> select id from t where rownum <= 1 for update skip locked;

        ID
----------
         1

SESSION2> select id from t where rownum <= 1 for update skip locked;

        ID
----------

Aucune ligne n'est renvoyée de la seconde sélection. Vous pouvez utiliser un curseur pour contourner ce problème:

SQL> CREATE FUNCTION get_and_lock RETURN NUMBER IS
  2     CURSOR c IS SELECT ID FROM t FOR UPDATE SKIP LOCKED;
  3     l_id NUMBER;
  4  BEGIN
  5     OPEN c;
  6     FETCH c INTO l_id;
  7     CLOSE c;
  8     RETURN l_id;
  9  END;
 10  /

Function created

SESSION1> variable x number;
SESSION1> exec :x := get_and_lock;

PL/SQL procedure successfully completed
x
---------
1

SESSION2> variable x number;
SESSION2> exec :x := get_and_lock;

PL/SQL procedure successfully completed
x
---------
2

Depuis que j'ai explicitement récupéré le curseur, une seule ligne sera renvoyée (et une seule ligne sera verrouillée).

17
Vincent Malgrat

Tandis que les autres réponses ont déjà expliqué suffisamment ce qui se passe dans votre base de données avec les différents SELECT .. FOR UPDATE Variantes, je pense qu'il convient de mentionner que Oracle décourage en utilisant FOR UPDATE SKIP LOCKED directement et encourage à utiliser Oracle AQ au lieu:

http://download.oracle.com/docs/cd/b28359_01/server.111/b28286/stavements_10002.htm#i2066346

Nous utilisons Oracle AQ Dans notre application et je peux confirmer que, après une courbe d'apprentissage quelque peu raide, il peut s'agir d'un moyen assez pratique de gérer directement les producteurs/consommateurs dans la base de données.

6
Lukas Eder

Ce n'est pas que la réponse de Vincent a mal mais je l'aurais conçu différemment.

Mon premier instinct est de sélectionner pour mettre à jour le premier enregistrement disponible et mettre à jour l'enregistrement avec un "réservé_date". Après que XXX Time a passé et que la transaction n'est pas finalisée, mettez à jour le record de l'enregistrement_date de retour à Null libérant l'enregistrement à nouveau.

J'essaie de garder les choses aussi simples que possible. Pour moi, c'est plus simple.

3
user734922