Vorwort
Unsere Anwendung führt mehrere Threads aus, die DELETE
Abfragen parallel ausführen . Die Abfragen wirken sich auf isolierte Daten aus, dh es sollte keine Möglichkeit bestehen, dass DELETE
in denselben Zeilen von separaten Threads gleichzeitig etwas auftritt. Gemäß der Dokumentation verwendet MySQL jedoch die sogenannte "Next-Key" -Sperre für DELETE
Anweisungen, die sowohl den passenden Schlüssel als auch eine gewisse Lücke sperrt. Diese Sache führt zu Deadlocks und die einzige Lösung, die wir gefunden haben, ist die Verwendung der READ COMMITTED
Isolationsstufe.
Das Problem
Problem tritt auf, wenn komplexe DELETE
Anweisungen mit JOIN
s großer Tabellen ausgeführt werden. In einem bestimmten Fall haben wir eine Tabelle mit Warnungen, die nur zwei Zeilen enthält, aber die Abfrage muss alle Warnungen, die zu bestimmten Entitäten gehören, aus zwei separaten INNER JOIN
ed-Tabellen löschen. Die Abfrage lautet wie folgt:
DELETE pw
FROM proc_warnings pw
INNER JOIN day_position dp
ON dp.transaction_id = pw.transaction_id
INNER JOIN ivehicle_days vd
ON vd.id = dp.ivehicle_day_id
WHERE vd.ivehicle_id=? AND dp.dirty_data=1
Wenn die Tabelle day_position groß genug ist (in meinem Testfall gibt es 1448 Zeilen), READ COMMITTED
blockiert jede Transaktion auch im Isolationsmodus die gesamte proc_warnings
Tabelle.
Das Problem wird immer in diesen Beispieldaten reproduziert - http://yadi.sk/d/QDuwBtpW1BxB9 sowohl in MySQL 5.1 (geprüft in 5.1.59) als auch in MySQL 5.5 (geprüft in MySQL 5.5.24).
BEARBEITEN: Die verknüpften Beispieldaten enthalten auch Schema und Indizes für die Abfragetabellen, die hier zur Vereinfachung wiedergegeben werden:
CREATE TABLE `proc_warnings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`transaction_id` int(10) unsigned NOT NULL,
`warning` varchar(2048) NOT NULL,
PRIMARY KEY (`id`),
KEY `proc_warnings__transaction` (`transaction_id`)
);
CREATE TABLE `day_position` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`transaction_id` int(10) unsigned DEFAULT NULL,
`sort_index` int(11) DEFAULT NULL,
`ivehicle_day_id` int(10) unsigned DEFAULT NULL,
`dirty_data` tinyint(4) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `day_position__trans` (`transaction_id`),
KEY `day_position__is` (`ivehicle_day_id`,`sort_index`),
KEY `day_position__id` (`ivehicle_day_id`,`dirty_data`)
) ;
CREATE TABLE `ivehicle_days` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`d` date DEFAULT NULL,
`sort_index` int(11) DEFAULT NULL,
`ivehicle_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `ivehicle_days__is` (`ivehicle_id`,`sort_index`),
KEY `ivehicle_days__d` (`d`)
);
Abfragen pro Transaktion lauten wie folgt:
Transaktion 1
set transaction isolation level read committed; set autocommit=0; begin; DELETE pw FROM proc_warnings pw INNER JOIN day_position dp ON dp.transaction_id = pw.transaction_id INNER JOIN ivehicle_days vd ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=2 AND dp.dirty_data=1;
Transaktion 2
set transaction isolation level read committed; set autocommit=0; begin; DELETE pw FROM proc_warnings pw INNER JOIN day_position dp ON dp.transaction_id = pw.transaction_id INNER JOIN ivehicle_days vd ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=13 AND dp.dirty_data=1;
Einer von ihnen schlägt immer mit dem Fehler "Wartezeit für Sperren überschritten ..." fehl. Das information_schema.innodb_trx
enthält folgende Zeilen:
| trx_id | trx_state | trx_started | trx_requested_lock_id | trx_wait_started | trx_wait | trx_mysql_thread_id | trx_query |
| '1A2973A4' | 'LOCK WAIT' | '2012-12-12 20:03:25' | '1A2973A4:0:3172298:2' | '2012-12-12 20:03:25' | '2' | '3089' | 'DELETE pw FROM proc_warnings pw INNER JOIN day_position dp ON dp.transaction_id = pw.transaction_id INNER JOIN ivehicle_days vd ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=13 AND dp.dirty_data=1' |
| '1A296F67' | 'RUNNING' | '2012-12-12 19:58:02' | NULL | NULL | '7' | '3087' | NULL |
information_schema.innodb_locks
| lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
| '1A2973A4:0:3172298:2' | '1A2973A4' | 'X' | 'RECORD' | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |
| '1A296F67:0:3172298:2' | '1A296F67' | 'X' | 'RECORD' | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |
Wie ich sehen kann, möchten beide Abfragen eine exklusive X
Sperre für eine Zeile mit Primärschlüssel = 53. Allerdings muss keine von beiden Zeilen aus der proc_warnings
Tabelle löschen . Ich verstehe nur nicht, warum der Index gesperrt ist. Darüber hinaus ist der Index weder gesperrt, wenn die proc_warnings
Tabelle leer ist, noch day_position
enthält die Tabelle weniger Zeilen (dh einhundert Zeilen).
Weitere Untersuchungen sollten EXPLAIN
die ähnliche SELECT
Abfrage durchlaufen. Es zeigt, dass das Abfrageoptimierungsprogramm keinen Index zum Abfragen von proc_warnings
Tabellen verwendet. Dies ist der einzige Grund, warum ich mir vorstellen kann, warum es den gesamten Primärschlüsselindex blockiert.
Vereinfachter Fall
Das Problem kann auch in einem einfacheren Fall reproduziert werden, wenn nur zwei Tabellen mit einigen Datensätzen vorhanden sind, die untergeordnete Tabelle jedoch keinen Index für die übergeordnete Tabellenreferenzspalte enthält.
parent
Tabelle erstellen
CREATE TABLE `parent` (
`id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
child
Tabelle erstellen
CREATE TABLE `child` (
`id` int(10) unsigned NOT NULL,
`parent_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
Tabellen füllen
INSERT INTO `parent` (id) VALUES (1), (2);
INSERT INTO `child` (id, parent_id) VALUES (1, NULL), (2, NULL);
Test in zwei parallelen Transaktionen:
Transaktion 1
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET AUTOCOMMIT=0; BEGIN; DELETE c FROM child c INNER JOIN parent p ON p.id = c.parent_id WHERE p.id = 1;
Transaktion 2
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET AUTOCOMMIT=0; BEGIN; DELETE c FROM child c INNER JOIN parent p ON p.id = c.parent_id WHERE p.id = 2;
In beiden Fällen ist gemeinsam, dass MySQL keine Indizes verwendet. Ich glaube, das ist der Grund für die Sperre des gesamten Tisches.
Unsere Lösung
Die einzige Lösung, die wir derzeit sehen können, besteht darin, das Standard-Wartezeitlimit für Sperren von 50 Sekunden auf 500 Sekunden zu erhöhen, damit der Thread die Bereinigung beendet. Dann drücken Sie die Daumen.
Jede Hilfe geschätzt.
day_position
Tabelle normalerweise, wenn sie so langsam ausgeführt wird, dass Sie das Zeitlimit auf 500 Sekunden erhöhen müssen? 2) Wie lange dauert die Ausführung, wenn Sie nur die Beispieldaten haben?