2010-08-30 8 views
13

Je suis intéressé si une requête select for update va verrouiller une ligne inexistante.Est-ce que «sélectionner pour la mise à jour» empêche d'autres connexions d'insertion lorsque la ligne n'est pas présent

par exemple.

Tableau FooBar avec deux colonnes, foo et bar, foo a un index unique

  • Problème requête select bar from FooBar where foo = ? for update
  • Si requête retourne aucune ligne
    • Problème requête insert into FooBar (foo, bar) values (?, ?)

Maintenant c'est possible e que l'insertion entraînerait une violation d'index ou le select for update empêche-t-il cela?

Intéressé par le comportement sur SQLServer (2005/8), Oracle et MySQL.

+0

Est-ce que MSSQL supporte même cette syntaxe? –

+0

Il a "avec updlock" qui je crois est à peu près la même chose. –

Répondre

7

Dans Oracle, SELECT ... FOR UPDATE n'a aucun effet sur une ligne inexistante (l'instruction déclenche simplement une exception No Data Found). L'instruction INSERT empêche la duplication des valeurs de clé uniques/primaires. Toute autre transaction tentant d'insérer les mêmes valeurs de clé se bloquera jusqu'à ce que la première transaction soit validée (à quel moment la transaction bloquée obtiendra une erreur de clé en double) ou annulera (à ce moment la transaction bloquée continue).

+0

Je voudrais répéter la partie à propos de "l'instruction soulève simplement une exception de non-données trouvées"; un SELECT ... FOR UPDATE peut renvoyer des lignes et n'a toujours aucun effet sur une ligne inexistante. ;) –

+0

Pas dans le cas de l'OP, où le select était sur foo = explicit_value, foo étant uniquement indexé. – DCookie

+0

Il est possible que l'instruction SELECT renvoie 0 ligne. Il ne soulève NO_DATA_FOUND que s'il est appelé avec INTO. – jva

0

SQL Server n'a que FOR UPDATE comme partie d'un curseur. Et, il s'applique uniquement aux instructions UPDATE qui sont associées à la ligne en cours dans le curseur. Par conséquent, le FOR UPDATE n'a aucune relation avec INSERT. Par conséquent, je pense que votre réponse est que ce n'est pas applicable dans SQL Server.

Maintenant, il peut être possible de simuler le comportement FOR UPDATE avec des transactions et des stratégies de verrouillage. Mais, cela peut être plus que ce que vous cherchez.

+0

Historiquement, il pouvait y avoir une différence de comportement (lorsque les mises à jour bloquaient toute la table) - mais pas au cours de la dernière décennie (et très probablement, pas depuis que FOR UPDATE a été introduit). – JulesLt

2

Sur Oracle:

Session 1

create table t (id number); 
alter table t add constraint pk primary key(id); 

SELECT * 
FROM t 
WHERE id = 1 
FOR UPDATE; 
-- 0 rows returned 
-- this creates row level lock on table, preventing others from locking table in exclusive mode 

Session 2

SELECT * 
FROM t 
FOR UPDATE; 
-- 0 rows returned 
-- there are no problems with locking here 

rollback; -- releases lock 


INSERT INTO t 
VALUES (1); 
-- 1 row inserted without problems 
+0

Je ne pense pas que 'SELECT FOR UPDATE' empêche un' INSERT' dans les deux sessions, juste un 'UPDATE'. –

6

MySQL

SELECT ... FOR UPDATE avec UPDATE

, une SELECT ... FOR UPDATE permet une session de verrouiller temporairement un enregistrement particulier les transactions, avec InnoDB (validation automatique désactivé) (ou dossiers) de sorte qu'aucune autre session peut mettre à jour il.Ensuite, au cours de la même transaction, la session peut effectivement exécuter un UPDATE sur le même enregistrement et valider ou annuler la transaction. Cela vous permettrait de verrouiller l'enregistrement afin qu'aucune autre session ne puisse le mettre à jour alors que vous avez peut-être une autre logique métier.

Ceci est accompli avec le verrouillage. InnoDB utilise des index pour verrouiller les enregistrements, donc le verrouillage d'un enregistrement existant semble facile - il suffit de verrouiller l'index pour cet enregistrement.

SELECT ... FOR UPDATE avec INSERT

Toutefois, pour utiliser SELECT ... FOR UPDATE avec INSERT, Comment verrouiller un index pour un enregistrement qui n'existe pas encore? Si vous utilisez le niveau d'isolement par défaut de REPEATABLE READ, InnoDB utilisera également les verrous gap. Tant que vous connaissez le id (ou même la gamme d'identifiants) à verrouiller, alors InnoDB peut verrouiller l'écart afin qu'aucun autre enregistrement ne puisse être inséré dans cet espace jusqu'à ce que nous en ayons fini avec lui.

Si votre colonne était un incrément-auto colonne id, puis SELECT ... FOR UPDATE avec INSERT INTO serait problématique parce que vous ne savez pas ce que le nouveau id était jusqu'à ce que vous l'avez inséré. Cependant, puisque vous connaissez le id que vous souhaitez insérer, SELECT ... FOR UPDATE avec INSERT fonctionnera.

CAVEAT

Sur le niveau d'isolation par défaut, SELECT ... FOR UPDATE sur un enregistrement inexistant ne fait pas bloc d'autres transactions. Donc, si deux transactions font SELECT ... FOR UPDATE sur le même enregistrement d'index inexistant, elles verront toutes deux le verrou, et aucune des deux transactions ne pourra mettre à jour l'enregistrement. En fait, si les deux essayent, une impasse sera détectée, et une sera annulée.

Par conséquent, si vous ne voulez pas faire face à une impasse, vous pourriez juste faire ce qui suit:

INSERT INTO ...

Démarrer une transaction et effectuer la INSERT. Faites votre logique métier et validez ou annulez la transaction. Dès que vous faites le INSERT sur l'index d'enregistrement inexistant sur la première transaction, toutes les autres transactions bloqueront si elles tentent INSERT un enregistrement avec le même index unique. Si la seconde transaction tente d'insérer un enregistrement avec le même index après que la première transaction ait validé l'insertion, une erreur "clé dupliquée" sera générée. Manipuler en conséquence.

SELECT ... LOCK IN SHARE MODE

Si vous sélectionnez avec LOCK IN SHARE MODE avant la INSERT, si une transaction antérieure a inséré ce dossier, mais n'a pas encore engagé, le SELECT ... LOCK IN SHARE MODE va bloquer jusqu'à ce que la transaction précédente a completé.

Donc, pour réduire les risques de double erreurs clés, surtout si vous tenez les verrous pendant un certain temps tout en effectuant la logique métier avant de les commettre ou les faire reculer:

  1. SELECT bar FROM FooBar WHERE foo = ? LOCK FOR UPDATE
  2. Si aucun enregistrement retourné, puis
  3. INSERT INTO FooBar (foo, bar) VALUES (?, ?)
+0

Généralement vrai, mais SELECT ... POUR UPDATE avec INSERT est néanmoins à mon humble avis inutile, au moins en MySQL. S'il vous plaît regardez [ici] (http://stackoverflow.com/a/31184293/3239842) pour une explication. – Binarus