2010-08-30 9 views
13

Ich bin interessiert, ob eine Abfrage select for update eine nicht vorhandene Zeile sperren wird."Wählen Sie für Update" verhindern andere Verbindungen einfügen, wenn die Zeile nicht vorhanden ist

z.B.

Tabelle FooBar mit zwei Spalten, foo und bar, hat foo einen eindeutigen Index

  • Ausgabe Abfrage select bar from FooBar where foo = ? for update
  • Wenn Abfrage keine Zeilen zurückgibt
    • Ausgabe Abfrage insert into FooBar (foo, bar) values (?, ?)

Jetzt ist es möglich e dass die Einfügung eine Indexverletzung verursachen würde oder verhindert die das?

Interesse an Verhalten auf SQLServer (2005/8), Oracle und MySQL.

+0

Does MSSQL auch diese Syntax unterstützen? –

+0

Es hat "mit updlock", was ich glaube, ist ziemlich das Gleiche. –

Antwort

7

In Oracle hat SELECT ... FOR UPDATE keine Auswirkungen auf eine nicht vorhandene Zeile (die Anweisung löst einfach eine No Data Found-Ausnahme aus). Die INSERT-Anweisung verhindert Duplikate von eindeutigen/primären Schlüsselwerten. Alle anderen Transaktionen, die versuchen, die gleichen Schlüsselwerte einzufügen, werden blockiert, bis die erste Transaktion festgeschrieben wird (zu diesem Zeitpunkt wird die blockierte Transaktion einen doppelten Schlüsselfehler erhalten), oder sie wird zurückgesetzt (zu diesem Zeitpunkt wird die blockierte Transaktion fortgesetzt).

+0

Ich würde den Teil über "die Anweisung wird einfach eine Nicht-Daten gefunden Exception ausgelöst"; Ein SELECT ... FOR UPDATE kann Zeilen zurückgeben und hat dennoch keine Auswirkungen auf eine nicht vorhandene Zeile. ;) –

+0

Nicht im Falle des OP angegeben, wo die Auswahl auf foo = explicit_value war, foo wird eindeutig indiziert. – DCookie

+0

Es ist möglich, dass die SELECT-Anweisung 0 Zeilen zurückgibt. Es wird nur NO_DATA_FOUND ausgelöst, wenn es mit INTO aufgerufen wird. – jva

0

SQL Server hat nur die FOR UPDATE als Teil eines Cursors. Und es gilt nur für UPDATE-Anweisungen, die der aktuellen Zeile im Cursor zugeordnet sind.

So hat die FOR UPDATE keine Beziehung mit INSERT. Daher ist Ihre Antwort, dass es in SQL Server nicht anwendbar ist.

Jetzt ist es möglich, das Verhalten FOR UPDATE mit Transaktionen und Sperrstrategien zu simulieren. Aber das könnte mehr sein als das, wonach Sie suchen.

+0

Historisch gesehen gab es möglicherweise einen Unterschied im Verhalten (wenn Updates die ganze Tabelle gesperrt haben) - aber nicht in der letzten Dekade (und möglicherweise nicht, seit FOR UPDATE eingeführt wurde). – JulesLt

2

On 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

Ich glaube nicht, SELECT für UPDATE verhindert eine 'INSERT' in jeder Sitzung, nur ein' UPDATE'. –

6

MySQL

SELECT ... FOR UPDATE mit UPDATE

Mit Transaktionen mit InnoDB (auto-commit ausgeschaltet), ein SELECT ... FOR UPDATE ermöglichen eine Sitzung vorübergehend einen bestimmten Datensatz nach unten zu sperren (oder Datensätze), so dass keine andere Sitzung aktualisieren es.Innerhalb derselben Transaktion kann die Sitzung dann einen UPDATE für denselben Datensatz ausführen und die Transaktion festschreiben oder zurücksetzen. Auf diese Weise können Sie den Datensatz sperren, sodass keine andere Sitzung ihn aktualisieren kann, während Sie möglicherweise eine andere Geschäftslogik ausführen.

Dies wird durch Sperren erreicht. InnoDB verwendet Indizes zum Sperren von Datensätzen, daher ist das Sperren eines vorhandenen Datensatzes einfach - sperren Sie einfach den Index für diesen Datensatz.

SELECT ... FOR UPDATE mit INSERT

, jedoch SELECT ... FOR UPDATE mit INSERT zu verwenden, wie sperren Sie einen Index für einen Datensatz, der noch nicht existiert? Wenn Sie die Standardisolationsstufe REPEATABLE READ verwenden, verwendet InnoDB auch die Lücke Sperren. Solange Sie die id (oder sogar den Bereich der IDs) zu sperren wissen, kann InnoDB die Lücke sperren, so dass kein anderer Datensatz in diese Lücke eingefügt werden kann, bis wir damit fertig sind.

Wenn Ihre id Spalte waren eine Autoinkrement-Spalte, dann SELECT ... FOR UPDATE mit INSERT INTO wäre problematisch, weil Sie nicht wissen, was die neue id war, bis Sie es eingefügt. Da Sie jedoch die id kennen, die Sie einfügen möchten, wird mit INSERT funktionieren.

CAVEAT

Auf der Standardisolationsstufe, SELECT ... FOR UPDATE auf einem nicht vorhandenen Datensatz tut nicht Block andere Transaktionen. Wenn also zwei Transaktionen eine für denselben nicht vorhandenen Indexdatensatz ausführen, erhalten beide die Sperre und keiner der Transaktionen kann den Datensatz aktualisieren. Wenn beide versuchen, wird ein Deadlock erkannt, und einer wird zurückgesetzt.

Deshalb, wenn Sie nicht mit einem Deadlock beschäftigen möchten, könnten Sie tun nur die folgenden:

INSERT INTO ...

Starten Sie eine Transaktion, und führen Sie die INSERT. Machen Sie Ihre Geschäftslogik und binden Sie die Transaktion entweder ein oder führen Sie einen Rollback durch. Sobald Sie den INSERT auf dem nicht existenten Datensatzindex für die erste Transaktion ausführen, blockieren alle anderen Transaktionen, wenn sie versuchen, einen Datensatz mit demselben eindeutigen Index zu . Wenn die zweite Transaktion versucht, einen Datensatz mit demselben Index einzufügen, nachdem die erste Transaktion die Einfügung festschreibt, wird ein Fehler "doppelter Schlüssel" angezeigt. Behandeln Sie entsprechend.

SELECT ... LOCK IN SHARE MODE

Wenn Sie mit LOCK IN SHARE MODE vor den INSERT wählen, wenn eine vorherige Transaktion, dass Datensatz eingefügt hat, aber noch nicht verpflichtet, bis zur vorherige Transaktion der SELECT ... LOCK IN SHARE MODE blockieren hat vervollständigt.

Also die Chance auf doppelten Schlüsselfehler zu reduzieren, vor allem wenn man die Schleusen für eine Weile während der Durchführung der Geschäftslogik halten, bevor sie begehen oder sie wieder rollen:

  1. SELECT bar FROM FooBar WHERE foo = ? LOCK FOR UPDATE
  2. Wenn keine Datensätze zurückgegeben, dann
  3. INSERT INTO FooBar (foo, bar) VALUES (?, ?)
+0

Allgemein stimmt, aber SELECT ... FOR UPDATE mit INSERT ist nichtsdestotrotz nutzlos IMHO, zumindest in MySQL. Bitte schauen Sie [hier] (http://stackoverflow.com/a/31184293/3239842) für eine Erklärung. – Binarus

Verwandte Themen