Was Sie wollen, ist SELECT ... FOR UPDATE im Kontext einer Transaktion. Mit SELECT FOR UPDATE werden die ausgewählten Zeilen exklusiv gesperrt, so als würden Sie UPDATE ausführen. Es wird auch implizit in der Isolationsstufe READ COMMITTED ausgeführt, unabhängig davon, auf welche Isolationsstufe die explizite Einstellung festgelegt ist. Beachten Sie jedoch, dass SELECT ... FOR UPDATE für die gleichzeitige Verwendung sehr schlecht ist und nur verwendet werden sollte, wenn dies unbedingt erforderlich ist. Es hat auch die Tendenz, sich in einer Codebasis zu vermehren, wenn Leute ausschneiden und einfügen.
Hier ist eine Beispielsitzung aus der Sakila-Datenbank, die einige der Verhaltensweisen von FOR UPDATE-Abfragen demonstriert.
Stellen Sie zunächst die Transaktionsisolationsstufe auf REPEATABLE READ ein, damit wir uns ganz klar fühlen. Dies ist normalerweise nicht erforderlich, da dies die Standardisolationsstufe für InnoDB ist:
session1> SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
session1> BEGIN;
session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA | WILLIAMS |
+------------+-----------+
1 row in set (0.00 sec)
Aktualisieren Sie in der anderen Sitzung diese Zeile. Linda heiratete und änderte ihren Namen:
session2> UPDATE customer SET last_name = 'BROWN' WHERE customer_id = 3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
Zurück in Session 1, weil wir in REPEATABLE READ waren, ist Linda immer noch LINDA WILLIAMS:
session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA | WILLIAMS |
+------------+-----------+
1 row in set (0.00 sec)
Aber jetzt wollen wir exklusiven Zugriff auf diese Zeile, deshalb rufen wir FOR UPDATE für die Zeile auf. Beachten Sie, dass wir jetzt die neueste Version der Zeile zurückerhalten, die in Sitzung2 außerhalb dieser Transaktion aktualisiert wurde. Das ist nicht REPEATABLE READ, das ist READ COMMITTED
session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3 FOR UPDATE;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA | BROWN |
+------------+-----------+
1 row in set (0.00 sec)
Lassen Sie uns die in session1 festgelegte Sperre testen. Beachten Sie, dass session2 die Zeile nicht aktualisieren kann.
session2> UPDATE customer SET last_name = 'SMITH' WHERE customer_id = 3;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
Aber wir können immer noch davon auswählen
session2> SELECT c.customer_id, c.first_name, c.last_name, a.address_id, a.address FROM customer c JOIN address a USING (address_id) WHERE c.customer_id = 3;
+-------------+------------+-----------+------------+-------------------+
| customer_id | first_name | last_name | address_id | address |
+-------------+------------+-----------+------------+-------------------+
| 3 | LINDA | BROWN | 7 | 692 Joliet Street |
+-------------+------------+-----------+------------+-------------------+
1 row in set (0.00 sec)
Eine untergeordnete Tabelle mit einer Fremdschlüsselbeziehung kann weiterhin aktualisiert werden
session2> UPDATE address SET address = '5 Main Street' WHERE address_id = 7;
Query OK, 1 row affected (0.05 sec)
Rows matched: 1 Changed: 1 Warnings: 0
session1> COMMIT;
Ein weiterer Nebeneffekt ist, dass Sie die Wahrscheinlichkeit eines Deadlocks erheblich erhöhen.
In Ihrem speziellen Fall möchten Sie wahrscheinlich:
BEGIN;
SELECT id FROM `items` WHERE `status`='pending' LIMIT 1 FOR UPDATE;
-- do some other stuff
UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `id`=<selected id>;
COMMIT;
Wenn das Teil "Anderes erledigen" unnötig ist und Sie eigentlich keine Informationen über die umliegende Zeile aufbewahren müssen, ist SELECT FOR UPDATE unnötig und verschwenderisch und Sie können stattdessen einfach ein Update ausführen:
UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `status`='pending' LIMIT 1;
Ich hoffe, das ergibt einen Sinn.
items
WHEREstatus
= 'pending' LIMIT 1 FOR UPDATE;" und sie sehen beide die gleiche Reihe, dann wird einer den anderen verriegeln. Ich hatte gehofft, dass es irgendwie in der Lage sein würde, die gesperrte Reihe zu umgehen und zum nächsten Gegenstand zu gelangen, der noch aussteht.