it-swarm.com.de

SELECT FOR UPDATE mit SQL Server

Ich verwende eine Microsoft SQL Server 2005-Datenbank mit Isolationsstufe READ_COMMITTED und READ_COMMITTED_SNAPSHOT=ON.

Jetzt möchte ich verwenden:

SELECT * FROM <tablename> FOR UPDATE

... damit andere Datenbankverbindungen blockieren, wenn versucht wird, auf dieselbe Zeile "FOR UPDATE" zuzugreifen.

Ich habe es versucht:

SELECT * FROM <tablename> WITH (updlock) WHERE id=1

... aber dies blockiert alle anderen Verbindungen, auch wenn eine andere ID als "1" ausgewählt wurde.

Welches ist der richtige Hinweis, um ein SELECT FOR UPDATE wie bekannt für Oracle, DB2, MySql?

EDIT 2009-10-03:

Mit den folgenden Anweisungen werden die Tabelle und der Index erstellt:

CREATE TABLE example ( Id BIGINT NOT NULL, TransactionId BIGINT, 
    Terminal BIGINT, Status SMALLINT );
ALTER TABLE example ADD CONSTRAINT index108 PRIMARY KEY ( Id )
CREATE INDEX I108_FkTerminal ON example ( Terminal )
CREATE INDEX I108_Key ON example ( TransactionId )

Viele parallele Prozesse tun dies SELECT:

SELECT * FROM example o WITH (updlock) WHERE o.TransactionId = ?

EDIT 2009-10-05:

Zur besseren Übersicht habe ich alle erprobten Lösungen in der folgenden Tabelle aufgeführt:

 Mechanismus | SELECT für verschiedene Zeilenblöcke | SELECT in gleichen Zeilenblöcken 
 ----------------------- + ---------------- ---------------- + -------------------------- 
 ROWLOCK | nein | nein 
 Updlock, Rowlock | ja | ja 
 xlock, rowlock | ja | ja 
 wiederholbargelesen | nein | nein 
 DBCC TRACEON (1211, -1) | ja | ja 
 rowlock, xlock, holdlock | ja | yes 
 Updlock, Holdlock | ja | ja 
 UPDLOCK, READPAST | nein | nein 
 
 Ich suche | nein | Ja
74
tangens

Vor kurzem hatte ich ein Deadlock-Problem , weil SQL Server mehr als nötig sperrt (Seite). Man kann nichts dagegen unternehmen. Jetzt gibt es Deadlock-Ausnahmen ... und ich wünschte, ich hätte stattdessen Oracle.

Edit: Wir verwenden mittlerweile die Snapshot-Isolation, die viele, aber nicht alle Probleme löst. Leider muss der Datenbankserver dies zulassen, um die Snapshot-Isolierung verwenden zu können, was zu unnötigen Problemen beim Kunden führen kann. Jetzt werden nicht nur Deadlock-Ausnahmen abgefangen (die natürlich immer noch auftreten können), sondern auch Probleme mit der gleichzeitigen Verwendung von Snapshots, um Transaktionen von Hintergrundprozessen zu wiederholen (die vom Benutzer nicht wiederholt werden können). Dies funktioniert aber immer noch viel besser als zuvor.

33

Ich habe ein ähnliches Problem, ich möchte nur 1 Zeile sperren. Soweit ich weiß, sperrt SQLSERVER mit der Option UPDLOCK alle Zeilen, die gelesen werden müssen, um die Zeile abzurufen. Wenn Sie also keinen Index für den direkten Zugriff auf die Zeile definieren, werden alle vorhergehenden Zeilen gesperrt. In deinem Beispiel:

Angenommen, Sie haben eine Tabelle namens TBL mit einem id -Feld. Sie möchten die Zeile mit id=10 Sperren. Sie müssen einen Index für die Feld-ID definieren (oder für alle anderen Felder, die an Ihrer Auswahl beteiligt sind):

CREATE INDEX TBLINDEX ON TBL ( id )

Und dann lautet Ihre Abfrage, NUR die von Ihnen gelesenen Zeilen zu sperren:

SELECT * FROM TBL WITH (UPDLOCK, INDEX(TBLINDEX)) WHERE id=10.

Wenn Sie die Option INDEX (TBLINDEX) nicht verwenden, muss SQLSERVER alle Zeilen ab dem Anfang der Tabelle lesen, um Ihre Zeile mit id=10 Zu finden, damit diese Zeilen gesperrt werden.

19
ManuelConde

Sie können nicht gleichzeitig Snapshot-Isolation und Blockieren von Lesevorgängen verwenden. Der Zweck der Snapshot-Isolierung besteht darin, zu verhindern Lesevorgänge zu blockieren.

7

vielleicht könnte man das Problem lösen, indem man mvcc permanent macht (im Gegensatz zu nur einem bestimmten Stapel: SET TRANSACTION ISOLATION LEVEL SNAPSHOT):

ALTER DATABASE yourDbNameHere SET READ_COMMITTED_SNAPSHOT ON;

[BEARBEITEN: 14. Oktober]

Nach dem Lesen dieses Dokuments: Bessere Parallelität in Oracle als in SQL Server? und http://msdn.Microsoft.com/en-us/library/ms175095.aspx

Wenn die Datenbankoption READ_COMMITTED_SNAPSHOT auf ON gesetzt ist, werden die Mechanismen zur Unterstützung der Option sofort aktiviert. Wenn Sie die Option READ_COMMITTED_SNAPSHOT festlegen, ist nur die Verbindung in der Datenbank zulässig, die den Befehl ALTER DATABASE ausführt. Es darf keine andere offene Verbindung in der Datenbank vorhanden sein, bis ALTER DATABASE abgeschlossen ist. Die Datenbank muss sich nicht im Einzelbenutzermodus befinden.

ich bin zu dem Schluss gekommen, dass Sie zwei Flags setzen müssen, um mssqls MVCC dauerhaft in einer bestimmten Datenbank zu aktivieren:

ALTER DATABASE yourDbNameHere SET ALLOW_SNAPSHOT_ISOLATION ON;
ALTER DATABASE yourDbNameHere SET READ_COMMITTED_SNAPSHOT ON;
5
Michael Buen

Try (Updlock, Rowlock)

5
BlueMonkMN

Die vollständige Antwort könnte sich mit den Interna des DBMS befassen. Dies hängt davon ab, wie das Abfragemodul (das den vom SQL-Optimierer generierten Abfrageplan ausführt) funktioniert.

Eine mögliche Erklärung (zumindest für einige DBMS-Versionen - nicht unbedingt für MS SQL Server) ist jedoch, dass die ID-Spalte keinen Index enthält, sodass jeder Prozess versucht, eine Abfrage mit 'WHERE id = ? 'führt am Ende einen sequentiellen Scan der Tabelle durch, und dieser sequentielle Scan trifft auf die Sperre, die Ihr Prozess angewendet hat. Sie können auch auf Probleme stoßen, wenn das DBMS standardmäßig Sperren auf Seitenebene anwendet. Durch das Sperren einer Zeile wird die gesamte Seite und alle Zeilen auf dieser Seite gesperrt.

Es gibt einige Möglichkeiten, wie Sie dies als Fehlerquelle entlarven können. Schauen Sie sich den Abfrageplan an. studiere die Indizes; Versuchen Sie Ihr SELECT mit der ID 1000000 anstelle von 1 und prüfen Sie, ob andere Prozesse noch blockiert sind.

5

OK, eine einzelne Auswahl wird standardmäßig die Transaktionsisolation "Read Committed" verwenden, die das Schreiben in diese Gruppe sperrt und daher stoppt. Mit können Sie die Transaktionsisolationsstufe ändern

Set Transaction Isolation Level { Read Uncommitted | Read Committed | Repeatable Read | Serializable }
Begin Tran
  Select ...
Commit Tran

Diese werden in SQL Server BOL ausführlich erläutert

Ihr nächstes Problem ist, dass SQL Server 2K5 die Sperren standardmäßig eskaliert, wenn Sie über mehr als ~ 2500 Sperren verfügen oder mehr als 40% des 'normalen' Speichers in der Sperrtransaktion verwenden. Die Eskalation geht zur Seite und dann zur Tabellensperre

Sie können diese Eskalation ausschalten, indem Sie "trace flag" 1211t setzen. Weitere Informationen finden Sie in BOL

3
TFD

Anwendungssperren sind eine Möglichkeit, Ihre eigenen Sperren mit benutzerdefinierter Granularität zu aktivieren und dabei eine "hilfreiche" Sperreneskalation zu vermeiden. Siehe sp_getapplock .

2
Constantin

Erstellen Sie ein gefälschtes Update, um die Rowlock-Funktion zu erzwingen.

UPDATE <tablename> (ROWLOCK) SET <somecolumn> = <somecolumn> WHERE id=1

Wenn das nicht Ihre Reihe verriegelt, weiß Gott, was wird.

Nach diesem "UPDATE" können Sie Ihre SELECT (ROWLOCK) und nachfolgende Aktualisierungen durchführen.

2
Feu

Ich gehe davon aus, dass Sie nicht möchten, dass eine andere Sitzung die Zeile liest, während diese bestimmte Abfrage ausgeführt wird ...

Wenn Sie SELECT in eine Transaktion einschließen, während Sie WITH (XLOCK, READPAST) als Sperrhinweis verwenden, erhalten Sie die gewünschten Ergebnisse. Stellen Sie einfach sicher, dass diese anderen gleichzeitigen Lesevorgänge nicht WITH (NOLOCK) verwenden. Mit READPAST können andere Sitzungen dasselbe SELECT ausführen, jedoch in anderen Zeilen.

BEGIN TRAN
  SELECT *
  FROM <tablename> WITH (XLOCK,READPAST) 
  WHERE RowId = @SomeId

  -- Do SOMETHING

  UPDATE <tablename>
  SET <column>[email protected]
  WHERE [email protected]
COMMIT
2
ewoo

Versuchen Sie es mit:

SELECT * FROM <tablename> WITH ROWLOCK XLOCK HOLDLOCK

Dadurch sollte die Sperre exklusiv werden und für die Dauer der Transaktion beibehalten werden.

1
RMorrisey

Ich habe das Rowlock-Problem auf eine ganz andere Art gelöst. Ich stellte fest, dass SQL Server eine solche Sperre nicht zufriedenstellend verwalten konnte. Ich habe mich entschieden, dies programmatisch mit einem Mutex zu lösen ... waitForLock ... releaseLock ...

1
jessn

Gemäß diesem Artikel besteht die Lösung darin, den WITH (REPEATABLEREAD) -Hinweis zu verwenden.

1
erikkallen

Sie müssen die Ausnahme zum Festschreibungszeitpunkt behandeln und die Transaktion wiederholen.

1
user205622

Frage - Ist dieser Fall nachweislich das Ergebnis einer Sperreneskalation (d. H. Wenn Sie mit dem Profiler Sperreneskalationsereignisse verfolgen, ist dies definitiv der Grund für die Blockierung)? In diesem Fall gibt es eine vollständige Erklärung und eine (ziemlich extreme) Problemumgehung, indem ein Ablaufverfolgungsflag auf Instanzebene aktiviert wird, um eine Sperreneskalation zu verhindern. Siehe http://support.Microsoft.com/kb/3236 Ablaufverfolgungsflag 1211

Aber das wird wahrscheinlich unbeabsichtigte Nebenwirkungen haben.

Wenn Sie eine Zeile absichtlich sperren und für einen längeren Zeitraum sperren, ist die Verwendung des internen Sperrmechanismus für Transaktionen nicht die beste Methode (zumindest in SQL Server). Alle Optimierungen in SQL Server sind auf kurze Transaktionen ausgerichtet - einsteigen, aktualisieren, aussteigen. Das ist der Grund für die Sperreneskalation.

Wenn also eine Zeile über einen längeren Zeitraum "ausgecheckt" werden soll, empfiehlt es sich, anstelle der Transaktionssperre eine Spalte mit Werten und eine einfache alte Update-Anweisung zu verwenden, um die Zeilen als gesperrt zu kennzeichnen oder nicht.

1
onupdatecascade

Überprüfen Sie alle Ihre Abfragen erneut. Vielleicht haben Sie eine Abfrage, die ohne ROWLOCK/FOR UPDATE-Hinweis aus derselben Tabelle auswählt, die Sie SELECT FOR UPDATE haben.


MSSQL eskaliert häufig diese Zeilensperren zu Sperren auf Seitenebene (selbst Sperren auf Tabellenebene, wenn Sie keinen Index für das Feld haben, das Sie abfragen), siehe Erklärung . Da Sie nach FOR UPDATE fragen, könnte ich davon ausgehen, dass Sie Robustheit auf Transaktionsniveau (z. B. Finanzen, Inventar usw.) benötigen. Die Ratschläge auf dieser Website gelten also nicht für Ihr Problem. Es ist nur eine Einsicht, warum MSSQL Sperren eskaliert .


Wenn Sie bereits MSSQL 2005 (und höher) verwenden, sind sie MVCC-basiert. Ich denke, Sie sollten kein Problem mit der Sperre auf Zeilenebene mit dem ROWLOCK/UPDLOCK-Hinweis haben. Wenn Sie jedoch bereits MSSQL 2005 oder höher verwenden, versuchen Sie, einige Ihrer Abfragen zu überprüfen, die dieselbe Tabelle abfragen, die Sie FOR UPDATE möchten, wenn sie Sperren eskalieren, indem Sie die Felder in ihrer WHERE-Klausel überprüfen, wenn sie über einen Index verfügen.


P.S.
Ich benutze PostgreSQL, es benutzt auch MVCC, habe FOR UPDATE, ich stoße nicht auf dasselbe Problem. Eskalationen von Sperren sind das, was MVCC löst. Daher wäre ich überrascht, wenn MSSQL 2005 weiterhin Sperren für Tabellen mit WHERE-Klauseln eskalieren würde, deren Felder keinen Index aufweisen. Wenn dies bei MSSQL 2005 immer noch der Fall ist (Sperrenausweitung), versuchen Sie, die Felder in WHERE-Klauseln zu überprüfen, wenn sie über einen Index verfügen.

Haftungsausschluss: Meine letzte Verwendung von MSSQL ist nur Version 2000.

1
Michael Buen

Haben Sie READPAST ausprobiert?

Ich habe UPDLOCK und READPAST zusammen verwendet, um eine Tabelle wie eine Warteschlange zu behandeln.

0
Gratzy

Wie wäre es, wenn Sie zunächst versuchen würden, ein einfaches Update für diese Zeile durchzuführen (ohne wirklich Daten zu ändern)? Danach können Sie mit der Zeile fortfahren, die zum Aktualisieren ausgewählt wurde.

UPDATE dbo.Customer SET FieldForLock = FieldForLock WHERE CustomerID = @CustomerID
/* do whatever you want */

Edit: Sie sollten es natürlich in eine Transaktion einwickeln

Edit 2: Eine andere Lösung ist die Verwendung der Isolationsstufe SERIALIZABLE

0
Vladimir