Wie kann die InnoDB DELETE-Leistung verbessert werden?

9

Ich habe also diese Prüftabelle (verfolgt Aktionen für jede Tabelle in meiner Datenbank):

CREATE TABLE `track_table` (
  `id` int(16) unsigned NOT NULL,
  `userID` smallint(16) unsigned NOT NULL,
  `tableName` varchar(255) NOT NULL DEFAULT '',
  `tupleID` int(16) unsigned NOT NULL,
  `date_insert` datetime NOT NULL,
  `action` char(12) NOT NULL DEFAULT '',
  `className` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `userID` (`userID`),
  KEY `tableID` (`tableName`,`tupleID`,`date_insert`),
  KEY `actionDate` (`action`,`date_insert`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1

und ich muss anfangen, veraltete Elemente zu archivieren. Die Tabelle ist auf ungefähr 50 Millionen Zeilen angewachsen. Der schnellste Weg, die Zeilen zu löschen, bestand darin, jeweils eine Tabelle (basierend auf tableName) zu löschen .

Dies funktioniert ziemlich gut, aber auf einigen Tabellen, die schreiblastig sind, wird es nicht vollständig. Meine Abfrage löscht alle Elemente, denen eine deleteAktion für eine Kombination aus tupleID und tableName zugeordnet ist:

DELETE FROM track_table WHERE tableName='someTable' AND tupleID IN (
  SELECT DISTINCT tupleID FROM track_table
  WHERE tableName='someTable' AND action='DELETE' AND date_insert < DATE_SUB(CURDATE(), INTERVAL 30 day)
)

Ich habe dies 3 Tage lang auf meinem Server laufen lassen und es wurde für die größte Tabelle nie abgeschlossen. Die EXPLAIN-Ausgabe (wenn ich das Löschen auf Auswahl umschalte:

| id | select_type        | table       | type | possible_keys      | key     | key_len | ref        | rows    | Extra                        |
|  1 | PRIMARY            | track_table | ref  | tableID            | tableID | 257     | const      | 3941832 | Using where                  |
|  2 | DEPENDENT SUBQUERY | track_table | ref  | tableID,actionDate | tableID | 261     | const,func |       1 | Using where; Using temporary |

Das Löschen von 4 Millionen Zeilen sollte also nicht 3 Tage dauern, würde ich denken. Ich habe meine innodb_buffer_pool_size auf 3 GB festgelegt, und der Server ist nicht für die Verwendung von one_file_per_table festgelegt. Welche anderen Möglichkeiten kann ich die Leistung beim Löschen von InnoDB verbessern? (Ausführen von MySQL 5.1.43 unter Mac OS X)

Derek Downey
quelle

Antworten:

11

Sie können Daten stapelweise löschen.

In SQL Server lautet die Syntax delete top XZeilen aus einer Tabelle. Sie tun dies dann in einer Schleife mit einer Transaktion für jeden Stapel (wenn Sie natürlich mehr als eine Anweisung haben), um Transaktionen kurz zu halten und Sperren nur für kurze Zeiträume aufrechtzuerhalten.

In der MySQL-Syntax: DELETE FROM userTable LIMIT 1000

LIMITDies unterliegt Einschränkungen (kann beispielsweise nicht bei Löschvorgängen mit Joins verwendet werden). In diesem Fall können Sie dies jedoch möglicherweise auf diese Weise tun.

Es gibt eine zusätzliche Gefahr für die Verwendung LIMITmit , DELETEwenn es um die Replikation; Die gelöschten Zeilen werden auf dem Slave manchmal nicht in derselben Reihenfolge gelöscht, in der sie auf dem Master gelöscht wurden.

Marian
quelle
6

Versuchen Sie es mit einem temporären Tabellenansatz. Versuchen Sie so etwas:

Schritt 1) CREATE TABLE track_table_new LIKE track_table;

Schritt 2) INSERT INTO track_table_new SELECT * FROM track_table WHERE action='DELETE' AND date_insert >= DATE_SUB(CURDATE(), INTERVAL 30 day);

Schritt 3) ALTER TABLE track_table RENAME track_table_old;

Schritt 4) ALTER TABLE track_table_new RENAME track_table;

Schritt 5) DROP TABLE track_table_old;

Ich habe das Tupelfeld in Schritt 2 nicht aufgenommen. Bitte prüfen Sie, ob dies den gewünschten Effekt erzeugt. Wenn Sie dies möchten, möchten Sie möglicherweise das Tupelfeld ganz löschen, es sei denn, Sie verwenden das Tupelfeld aus anderen Gründen.

RolandoMySQLDBA
quelle
Das ist eine interessante Lösung. Ich brauche das Tupelfeld in der Tabelle. tableName / tupleID ist ein undefinierter Fremdschlüssel der zu protokollierenden Tabelle. Undefiniert, da diese Tabelle bis vor kurzem MyISAM war, das keine Fremdschlüssel unterstützt.
Derek Downey
1

Das Löschen unerwünschter Zeilen im Stapel sollte andere Vorgänge funktionsfähig halten. Für das Löschen des Vorgangs gelten jedoch Bedingungen. Stellen Sie daher sicher, dass für Spalten über Bedingungen ein geeigneter Index vorhanden ist.

Da MySQL die vollständige Funktion des losen Index-Scans nicht unterstützt, können Sie versuchen, die Reihenfolge für KEY actionDate (action, date_insert)bis anzupassen KEY actionDate (date_insert, action). Mit dem Präfix 'date_insert' sollte MySQL diesen Index verwenden, um die Zeilen zu scannen, die vor Ihrer datetime-Bedingung liegen.

Mit einem solchen Index können Sie SQL schreiben als:

DELETE
FROM track_table
WHERE tableName='someTable'
    AND action='DELETE'
    AND date_insert < DATE_SUB(CURDATE(), INTERVAL 30 day)
LIMIT 1000 -- Your size of batch
Mike Lue
quelle
1
| id | select_type        | table       | type | possible_keys      | key     | key_len | ref        | rows    | Extra                        |
|  1 | PRIMARY            | track_table | ref  | tableID            | tableID | 257     | const      | 3941832 | Using where                  |
|  2 | DEPENDENT SUBQUERY | track_table | ref  | tableID,actionDate | tableID | 261     | const,func |       1 | Using where; Using temporary |

-Fist, aus Ihrer Erklärung der key_len so groß => müssen Sie die Größe so klein wie möglich herabstufen. Für Ihre Abfrage ist es meiner Meinung nach am besten, den Datentyp des Aktionsfelds von char (12) in tinyint zu ändern, sodass die Datenzuordnung folgendermaßen aussieht:

1: -> DELETE
2: -> UPDATE
3: -> INSERT
...

und Sie können anstelle des Tabellennamens auch table_id ändern. Die DDL für die beste Leistung kann:

CREATE TABLE `track_table` (
  `id` int(11) unsigned NOT NULL,
  `userID` smallint(6) unsigned NOT NULL,
  `tableid` smallint(6) UNSIGNED NOT NULL DEFAULT 0,
  `tupleID` int(11) unsigned NOT NULL,
  `date_insert` datetime NOT NULL,
  `actionid` tinyin(4) UNSIGNED NOT NULL DEFAULT 0,
  `className` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `userID` (`userID`),
  KEY `tableID` (`tableid`,`tupleID`,`date_insert`),
  KEY `actionDate` (`actionid`,`date_insert`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE `actions` (
  `id` tinyint(4) unsigned NOT NULL 
  `actionname` varchar(255) NOT NULL,
  PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE `table_name` (
  `id` tinyint(4) unsigned NOT NULL 
  `tablename` varchar(255) NOT NULL,
  PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Die Abfrage kann also folgendermaßen aussehen:

DELETE FROM track_table WHERE tableid=@tblid AND tupleID IN (
  SELECT DISTINCT tupleID FROM track_table
  WHERE tableid=@tblid AND actionid=@actionid AND date_insert < DATE_SUB(CURDATE(), INTERVAL 30 day)
).

Der schnellste Weg war jedoch die Verwendung der Partition. So können Sie Partition löschen. Derzeit hat mein Tisch mehr als 40mil Zeilen. und stündlich aktualisieren (400.000 Zeilen werden jedes Mal aktualisiert), und ich kann die curr_date-Partition löschen und Daten in die Tabelle neu laden. Der Drop-Befehl ist sehr schnell (<100 ms). Ich hoffe das hilft.

Thanh Nguyen
quelle