Dies ist kein vollständiger / korrekter MySQL-Abfrage-Pseudocode:
Select *
from Notifications as n
where n.date > (CurrentDate-10 days)
limit by 1
FOR UPDATE
http://dev.mysql.com/doc/refman/5.0/en/select.html besagt: Wenn Sie FOR UPDATE mit einer Speicher-Engine verwenden, die Seiten- oder Zeilensperren verwendet, werden die von der Abfrage untersuchten Zeilen bis zum Ende der aktuellen Transaktion
Ist hier nur der eine von MySQL zurückgegebene Datensatz gesperrt oder alle Datensätze, die gescannt werden müssen, um den einzelnen Datensatz zu finden?
all Records it has to SCAN TO FIND the SINGLE RECORD
wäre so schrecklich dumm, dass ich wirklich bezweifle, dass MySQL so funktioniert. Denken Sie an den Algorithmus in der MySQL-Suchmaschine - wenn er eine Zeile sieht und weiß, dass es nicht die Zeile ist, die Sie benötigen, warum sollte er dann zusätzliche Zeit für das Setzen der Sperre aufwenden?! Ich schlage vor, dass Sie die Antwort nicht akzeptieren, damit andere MySQL-Leute dies kommentieren könnenWHERE
Bedingung erfüllen . Daher ist es technisch möglich und ich denke nicht, dass MySQL so viel minderwertiger istAntworten:
Warum versuchen wir es nicht einfach?
Richten Sie die Datenbank ein
CREATE DATABASE so1; USE so1; CREATE TABLE notification (`id` BIGINT(20), `date` DATE, `text` TEXT) ENGINE=InnoDB; INSERT INTO notification(id, `date`, `text`) values (1, '2011-05-01', 'Notification 1'); INSERT INTO notification(id, `date`, `text`) values (2, '2011-05-02', 'Notification 2'); INSERT INTO notification(id, `date`, `text`) values (3, '2011-05-03', 'Notification 3'); INSERT INTO notification(id, `date`, `text`) values (4, '2011-05-04', 'Notification 4'); INSERT INTO notification(id, `date`, `text`) values (5, '2011-05-05', 'Notification 5');
Starten Sie nun zwei Datenbankverbindungen
Verbindung 1
BEGIN; SELECT * FROM notification WHERE `date` >= '2011-05-03' FOR UPDATE;
Verbindung 2
BEGIN;
Wenn MySQL alle Zeilen sperrt, wird die folgende Anweisung blockiert. Wenn nur die zurückgegebenen Zeilen gesperrt werden, sollte es nicht blockiert werden.
SELECT * FROM notification WHERE `date` = '2011-05-02' FOR UPDATE;
Und tatsächlich blockiert es.
Interessanterweise können wir auch keine Datensätze hinzufügen, die gelesen werden würden, d. H.
INSERT INTO notification(id, `date`, `text`) values (6, '2011-05-06', 'Notification 6');
Blöcke auch!
Ich kann zum jetzigen Zeitpunkt nicht sicher sein, ob MySQL nur die gesamte Tabelle sperrt, wenn ein bestimmter Prozentsatz der Zeilen gesperrt ist, oder ob es wirklich intelligent ist, um sicherzustellen, dass das Ergebnis der
SELECT ... FOR UPDATE
Abfrage niemals durch eine andere Transaktion geändert werden kann ( mit einemINSERT
,,UPDATE
oderDELETE
), während das Schloss gehalten wird.quelle
id
Spalten aus, ich vertraue Datumsliteralen nicht :). Was ist die Version von MySQL / InnoDB?CREATE TABLE notification (
id` BIGINT (20) NICHT NULL AUTO_INCREMENT,date
DATE,text
TEXT, PRIMARY KEY (id
)) ENGINE = InnoDB; `was funktioniert. Es funktioniert jedoch nicht, wenn kein eindeutiger / Primärschlüssel angegeben ist, z.SELECT * FROM notification WHERE
id` = '1' FOR UPDATE; `auf dem ursprünglichen Schemafor update
von where-Filtern für nicht indizierte Spalten zu einer Sperre der gesamten Tabelle führt, während die Verwendung von where-Filtern für indizierte Spalten zu dem gewünschten Verhalten der gefilterten Zeilensperrung führt. Ein Primärschlüssel ist also nicht erforderlich, jeder Schlüssel reicht aus.for update
einen where-Filter ausführen , der zu einem leeren Satz führt, wird dadurch keine weitere Abfrage für denselben leeren Satz blockiert. Während ohne Index die Ausführungfor update
die Abfrage für denselben leeren Satz blockiert.Ich weiß, dass diese Frage ziemlich alt ist, aber ich wollte die Ergebnisse einiger relevanter Tests, die ich mit indizierten Spalten durchgeführt habe, teilen, was zu ziemlich seltsamen Ergebnissen geführt hat.
Tabellenstruktur:
CREATE TABLE `t1` ( `id` int(11) NOT NULL AUTO_INCREMENT, `notid` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
12 Zeilen eingefügt mit
INSERT INTO t1 (notid) VALUES (1), (2),..., (12)
. Bei Verbindung 1 :BEGIN; SELECT * FROM t1 WHERE id=5 FOR UPDATE;
Bei Verbindung 2 werden folgende Anweisungen blockiert:
SELECT * FROM t1 WHERE id!=5 FOR UPDATE; SELECT * FROM t1 WHERE id<5 FOR UPDATE; SELECT * FROM t1 WHERE notid!=5 FOR UPDATE; SELECT * FROM t1 WHERE notid<5 FOR UPDATE; SELECT * FROM t1 WHERE id<=4 FOR UPDATE;
Das Merkwürdigste ist , dass
SELECT * FROM t1 WHERE id>5 FOR UPDATE;
wird nicht blockiert , noch sind irgendwelche... SELECT * FROM t1 WHERE id=3 FOR UPDATE; SELECT * FROM t1 WHERE id=4 FOR UPDATE; SELECT * FROM t1 WHERE id=6 FOR UPDATE; SELECT * FROM t1 WHERE id=7 FOR UPDATE; ...
Ich möchte auch darauf hinweisen, dass anscheinend die gesamte Tabelle gesperrt ist, wenn die
WHERE
Bedingung in der Abfrage von Verbindung 1 mit einer nicht indizierten Zeile übereinstimmt. Wenn beispielsweise Verbindung 1 ausgeführt wirdSELECT * FROM t1 WHERE notid=5 FOR UPDATE
, werden alle ausgewählten Abfragen mitFOR UPDATE
undUPDATE
Abfragen von Verbindung 2 blockiert.- BEARBEITEN -
Dies ist eine ziemlich spezifische Situation, aber es war die einzige, die ich finden konnte, die dieses Verhalten zeigt:
Verbindung 1:
BEGIN; SELECT *, @x:=@x+id AS counter FROM t1 CROSS JOIN (SELECT @x:=0) b HAVING counter>5 LIMIT 1 FOR UPDATE; +----+-------+-------+---------+ | id | notid | @x:=0 | counter | +----+-------+-------+---------+ | 3 | 3 | 0 | 9 | +----+-------+-------+---------+ 1 row in set (0.00 sec)
Ab Verbindung 2 :
SELECT * FROM t1 WHERE id=2 FOR UPDATE;
ist blockiert;SELECT * FROM t1 WHERE id=4 FOR UPDATE;
ist nicht blockiert.quelle
Der Thread ist ziemlich alt, nur um meine zwei Cent in Bezug auf die oben von @Frans durchgeführten Tests zu teilen
Verbindung 1
BEGIN; SELECT * FROM notification WHERE `date` >= '2011-05-03' FOR UPDATE;
Verbindung 2
BEGIN; SELECT * FROM notification WHERE `date` = '2011-05-02' FOR UPDATE;
Die gleichzeitige Transaktion 2 wird sicher blockiert, aber der Grund ist NICHT, dass die Transaktion 1 die Sperre für den gesamten Tisch hält. Im Folgenden wird erklärt, was hinter den Kulissen passiert ist:
Zunächst ist die Standardisolationsstufe der InnoDB-Speicher-Engine
Repeatable Read
. In diesem Fall,1- Wenn die Spalte, in der die Bedingung verwendet wird, nicht indiziert ist (wie im obigen Fall):
Die Engine muss einen vollständigen Tabellenscan durchführen, um die Datensätze herauszufiltern, die nicht den Kriterien entsprechen. JEDE gescannte REIHE wird an erster Stelle gesperrt. MySQL kann die Sperren für diese Datensätze aufheben, die später nicht mit der where-Klausel übereinstimmen. Es ist eine Optimierung für die Leistung, jedoch verstößt ein solches Verhalten gegen die 2PL-Einschränkung.
Wenn Transaktion 2 wie erläutert gestartet wird, muss sie die X-Sperre für jede abgerufene Zeile abrufen, obwohl nur ein einziger Datensatz (id = 2) vorhanden ist, der mit der where-Klausel übereinstimmt. Schließlich wartet die Transaktion 2 auf die X-Sperre der ersten Zeile (id = 1), bis die Transaktion 1 festgeschrieben oder zurückgesetzt wird.
2- Wenn die Spalte, in der Bedingung verwendet wird, ein Primärindex ist
Nur der Indexeintrag, der die Kriterien erfüllt, ist gesperrt. Deshalb sagt jemand in den Kommentaren, dass einige Tests nicht blockiert sind.
3 - Wenn die Spalte, in der die Bedingung verwendet wird, ein Index ist, der jedoch nicht eindeutig ist
Dieser Fall ist komplizierter. 1) Der Indexeintrag ist gesperrt. 2) Eine X-Sperre ist an den entsprechenden Primärindex angehängt. 3) Unmittelbar vor und nach dem Datensatz, der den Suchkriterien entspricht, werden zwei Lückenschlösser an die nicht vorhandenen Einträge angehängt.
quelle
Wenn Sie den Links auf der von Ihnen veröffentlichten Dokumentationsseite folgen, erhalten Sie weitere Informationen zum Sperren . Auf dieser Seite
Dies scheint ziemlich klar zu sein, dass alle Zeilen gescannt werden müssen.
quelle
WHERE
nicht indiziert sind, die gesamte Tabelle gesperrt wird? Das ist offensichtlich falsch. Zumindest sperrt Oracle nurselect
ed Zeilensetting exclusive locks on each row it reads
kann anders interpretiert werden. Obwohl ich zustimme, ist es nicht 100% klarAus dem offiziellen MySQL-Dokument:
Für den in Frans 'Antwort diskutierten Fall sind alle Zeilen gesperrt, da während der SQL-Verarbeitung ein Tabellenscan durchgeführt wird:
Überprüfen Sie das neueste Dokument hier: https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html
quelle
Wie bereits erwähnt, sperrt SELECT ... FOR UPDATE alle Zeilen, die in der Standardisolationsstufe gefunden wurden. Versuchen Sie, die Isolation für die Sitzung, in der diese Abfrage ausgeführt wird, auf READ COMMITTED zu setzen. Stellen Sie der Abfrage beispielsweise Folgendes voraus:
set session transaction isolation level read committed;
quelle
Es sperrt alle durch Abfrage ausgewählten Zeilen.
quelle
SELECT
ed-Zeilen.