it-swarm.com.de

InnoDB-Zeilensperrung - Implementierung

Ich habe mich jetzt umgesehen, die MySQL-Site gelesen und kann immer noch nicht genau sehen, wie es funktioniert.

Ich möchte das Ergebnis zum Schreiben auswählen und in einer Reihe sperren, die Änderung schreiben und die Sperre aufheben. Audocommit ist aktiviert.

planen

id (int)
name (varchar50)
status (enum 'pending', 'working', 'complete')
created (datetime)
updated (datetime) 

Wählen Sie ein Element mit dem Status "Ausstehend" aus und aktualisieren Sie es auf "Funktionieren". Verwenden Sie ein exklusives Schreiben, um sicherzustellen, dass derselbe Artikel nicht zweimal abgeholt wird.

damit;

"SELECT id FROM `items` WHERE `status`='pending' LIMIT 1 FOR WRITE"

holen Sie sich die ID aus dem Ergebnis

"UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `id`=<selected id>

Muss ich etwas tun, um die Sperre aufzuheben, und funktioniert es wie oben beschrieben?

13
Wizzard

Was Sie wollen, ist SELECT ... FOR UPDATE aus dem Kontext einer Transaktion. SELECT FOR UPDATE sperrt die ausgewählten Zeilen exklusiv, als würden Sie UPDATE ausführen. Es wird auch implizit in der Isolationsstufe READ COMMITTED ausgeführt, unabhängig davon, auf welche Isolationsstufe explizit festgelegt wurde. Beachten Sie jedoch, dass SELECT ... FOR UPDATE für die Parallelität 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 glasklar sind. 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 Sitzung 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, also rufen wir FOR UPDATE für die Zeile auf. Beachten Sie, dass wir jetzt die neueste Version der Zeile zurückerhalten, die in Sitzung 2 außerhalb dieser Transaktion aktualisiert wurde. Das ist nicht WIEDERHOLBAR LESEN, das ist LESEN VERPFLICHTET

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)

Testen wir die in Sitzung1 festgelegte Sperre. 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 daraus 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)

Und wir können immer noch eine untergeordnete Tabelle mit einer Fremdschlüsselbeziehung aktualisieren

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 "andere Dinge erledigen" nicht erforderlich ist und Sie 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;

Hoffe das macht Sinn.

26
Aaron Brown

Wenn Sie die InnoDB-Speicher-Engine verwenden, wird die Sperre auf Zeilenebene verwendet. In Verbindung mit der Mehrfachversionierung führt dies zu einer guten Parallelität der Abfragen, da eine bestimmte Tabelle von verschiedenen Clients gleichzeitig gelesen und geändert werden kann. Die Parallelitätseigenschaften auf Zeilenebene lauten wie folgt:

Verschiedene Clients können dieselben Zeilen gleichzeitig lesen.

Verschiedene Clients können verschiedene Zeilen gleichzeitig ändern.

Verschiedene Clients können nicht dieselbe Zeile gleichzeitig ändern. Wenn eine Transaktion eine Zeile ändert, können andere Transaktionen dieselbe Zeile erst ändern, wenn die erste Transaktion abgeschlossen ist. Andere Transaktionen können die geänderte Zeile ebenfalls nicht lesen, es sei denn, sie verwenden die Isolationsstufe READ UNCOMMITTED. Das heißt, sie sehen die ursprüngliche unveränderte Zeile.

Grundsätzlich müssen Sie keine explizite Sperre angeben. InnoDB behandelt dies selbst, obwohl Sie in einigen Situationen möglicherweise explizite Sperrdetails zur expliziten Sperre angeben müssen:

In der folgenden Liste werden die verfügbaren Sperrtypen und ihre Auswirkungen beschrieben:

LIES

Sperrt eine Tabelle zum Lesen. Eine READ-Sperre sperrt eine Tabelle für Leseabfragen wie SELECT, die Daten aus der Tabelle abrufen. Schreibvorgänge wie INSERT, DELETE oder UPDATE, die die Tabelle ändern, sind nicht zulässig, auch nicht von dem Client, der die Sperre besitzt. Wenn eine Tabelle zum Lesen gesperrt ist, können andere Clients gleichzeitig aus der Tabelle lesen, aber kein Client kann darauf schreiben. Ein Client, der in eine Tabelle schreiben möchte, die durch Lesen gesperrt ist, muss warten, bis alle Clients, die gerade daraus lesen, ihre Sperren abgeschlossen und freigegeben haben.

SCHREIBE

Sperrt eine Tabelle zum Schreiben. Ein WRITE-Schloss ist ein exklusives Schloss. Es kann nur erworben werden, wenn eine Tabelle nicht verwendet wird. Einmal erfasst, kann nur der Client, der die Schreibsperre hält, aus der Tabelle lesen oder in diese schreiben. Andere Clients können weder lesen noch darauf schreiben. Kein anderer Client kann die Tabelle zum Lesen oder Schreiben sperren.

LOCAL LESEN

Sperrt eine Tabelle zum Lesen, ermöglicht jedoch gleichzeitige Einfügungen. Eine gleichzeitige Einfügung ist eine Ausnahme vom Prinzip "Leser blockieren Schriftsteller". Dies gilt nur für MyISAM-Tabellen. Wenn eine MyISAM-Tabelle keine Löcher in der Mitte aufweist, die aus gelöschten oder aktualisierten Datensätzen resultieren, werden Einfügungen immer am Ende der Tabelle vorgenommen. In diesem Fall kann ein Client, der aus einer Tabelle liest, diese mit einer READ LOCAL-Sperre sperren, damit andere Clients in die Tabelle einfügen können, während der Client, der die Lesesperre hält, daraus liest. Wenn eine MyISAM-Tabelle Löcher aufweist, können Sie diese entfernen, indem Sie OPTIMIZE TABLE verwenden, um die Tabelle zu defragmentieren.

2
Mahesh Patil

Eine andere Alternative wäre, eine Spalte hinzuzufügen, in der die Zeit der letzten erfolgreichen Sperre gespeichert ist, und dann müsste alles andere, was die Zeile sperren möchte, warten, bis sie entweder gelöscht wurde oder 5 Minuten (oder was auch immer) vergangen sind.

Etwas wie...

Schema

id (int)
name (varchar50)
status (enum 'pending', 'working', 'complete')
created (datetime)
updated (datetime)
lastlock (int)

lastlock ist ein int, da es den Unix-Zeitstempel speichert, da er einfacher (und möglicherweise schneller) zu vergleichen ist.

// Entschuldigen Sie die Semantik, ich habe nicht überprüft, ob sie tatsächlich ausgeführt werden, aber sie sollten nah genug sein, wenn sie dies nicht tun.

UPDATE items 
  SET lastlock = UNIX_TIMESTAMP() 
WHERE 
  lastlock = 0
  OR (UNIX_TIMESTAMP() - lastlock) > 360;

Überprüfen Sie dann, wie viele Zeilen aktualisiert wurden, da Zeilen nicht von zwei Prozessen gleichzeitig aktualisiert werden können. Wenn Sie die Zeile aktualisiert haben, haben Sie die Sperre erhalten. Angenommen, Sie verwenden PHP, würden Sie mysql_affected_rows () verwenden. Wenn die Rückgabe 1 war, haben Sie es erfolgreich gesperrt.

Dann können Sie entweder die letzte Sperre auf 0 aktualisieren, nachdem Sie das getan haben, was Sie tun müssen, oder faul sein und 5 Minuten warten, bis der nächste Sperrversuch trotzdem erfolgreich ist.

BEARBEITEN: Möglicherweise müssen Sie ein wenig arbeiten, um zu überprüfen, ob es bei Änderungen im Sommer wie erwartet funktioniert, da die Uhren eine Stunde zurückgehen und die Prüfung möglicherweise ungültig wird. Sie müssen sicherstellen, dass sich die Unix-Zeitstempel in UTC befinden - was möglicherweise auch der Fall ist.

0
Steve Childs