Frage

Ich bin ein relativ Anfänger, wenn es um Datenbanken kommt. Wir verwenden MySQL und ich versuche zur Zeit eine SQL-Anweisung, die eine Weile dauern, scheint zu beschleunigen zu laufen. Ich suchte eine ähnliche Frage auf SO herum, aber nicht fündig wird.

Das Ziel ist es, alle Zeilen in der Tabelle A zu entfernen, die eine passende ID in Tabelle B haben.

ich zur Zeit mache folgendes:

DELETE FROM a WHERE EXISTS (SELECT b.id FROM b WHERE b.id = a.id);

Es gibt etwa 100 K Zeilen in der Tabelle ein und etwa 22K Zeilen in der Tabelle b. Die Spalte ‚id‘ ist die PK für beiden Tabellen.

Diese Aussage dauert ca. 3 Minuten auf meiner Test-Box läuft - Pentium D, XP SP3, 2 GB RAM, MySQL 5.0.67. Dies scheint langsam zu mir. Vielleicht ist es nicht, aber ich hatte gehofft, dass die Dinge zu beschleunigen. Gibt es einen besseren / schnelleren Weg dies zu tun?


EDIT:

Einige zusätzliche Informationen, die hilfreich sein könnten. Tabellen A und B haben die gleiche Struktur wie ich folgendes getan haben Tabelle B zu erstellen:

CREATE TABLE b LIKE a;

Table a (und damit Tabelle b) hat ein paar Indizes zu beschleunigen Abfragen zu helfen, die gegen sie gemacht werden. Auch hier bin ich bei der DB Arbeit ein relativer Neuling und immer noch lernen. Ich weiß nicht, wie viel von einem Effekt, wenn überhaupt, das auf die Dinge hat. Ich gehe davon aus, dass es eine Wirkung hat als die Indizes haben auch gereinigt werden, nicht wahr? Ich frage mich auch, ob es irgendwelche anderen DB-Einstellungen waren, die die Geschwindigkeit beeinflussen könnten.

Auch ich bin mit INNO DB.


Hier finden Sie einige zusätzliche Informationen, die Ihnen nützlich sein könnten.

Tabelle A eine ähnliche Struktur wie diese hat (ich habe das ein bisschen hygienisiert):

DROP TABLE IF EXISTS `frobozz`.`a`;
CREATE TABLE  `frobozz`.`a` (
  `id` bigint(20) unsigned NOT NULL auto_increment,
  `fk_g` varchar(30) NOT NULL,
  `h` int(10) unsigned default NULL,
  `i` longtext,
  `j` bigint(20) NOT NULL,
  `k` bigint(20) default NULL,
  `l` varchar(45) NOT NULL,
  `m` int(10) unsigned default NULL,
  `n` varchar(20) default NULL,
  `o` bigint(20) NOT NULL,
  `p` tinyint(1) NOT NULL,
  PRIMARY KEY  USING BTREE (`id`),
  KEY `idx_l` (`l`),
  KEY `idx_h` USING BTREE (`h`),
  KEY `idx_m` USING BTREE (`m`),
  KEY `idx_fk_g` USING BTREE (`fk_g`),
  KEY `fk_g_frobozz` (`id`,`fk_g`),
  CONSTRAINT `fk_g_frobozz` FOREIGN KEY (`fk_g`) REFERENCES `frotz` (`g`)
) ENGINE=InnoDB AUTO_INCREMENT=179369 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

Ich vermute, dass ein Teil des Problems ist eine Reihe von Indizes für diese Tabelle gibt. Tabelle B sieht ähnlich aus wie die Tabelle B, obwohl es nur die Spalten id und h enthält.

Auch die Profilierung ergibt sich wie folgt:

starting 0.000018
checking query cache for query 0.000044
checking permissions 0.000005
Opening tables 0.000009
init 0.000019
optimizing 0.000004
executing 0.000043
end 0.000005
end 0.000002
query end 0.000003
freeing items 0.000007
logging slow query 0.000002
cleaning up 0.000002

GELöST

Danke an alle Antworten und Kommentare. Sie haben mich sicher über das Problem nachzudenken. Ein großes Lob an dotjoe für mich immer von dem Problem zu Schritt weg von der einfachen Frage: „Haben alle anderen Tabellen a.id verweisen?“

Das Problem war, dass es ein DELETE-Trigger auf Tabelle A war, die eine gespeicherte Prozedur namens zwei weitere Tabellen zu aktualisieren, C und D. Tabelle C einen FK mußten zurück nach a.id und nach einem paar Sachen zu tun, um diese ID im Zusammenhang in die gespeicherte Prozedur, es hatte die Anweisung,

DELETE FROM c WHERE c.id = theId;

Ich schaute in die EXPLAIN-Anweisung und schrieb dies als,

EXPLAIN SELECT * FROM c WHERE c.other_id = 12345;

So konnte ich sehen, was dieser tat, und es gab mir die folgenden Informationen:

id            1
select_type   SIMPLE
table         c
type          ALL
possible_keys NULL
key           NULL
key_len       NULL
ref           NULL
rows          2633
Extra         using where

Das sagte mir, dass es sich um eine schmerzhafte Operation ist zu machen und da es würde zu 22500 mal aufgerufen (für die gegebenen Datensatz gelöscht werden), das ist das Problem. Sobald ich einen Index auf dieser Spalte other_id erstellt und reran die ERKLÄREN, ich habe:

id            1
select_type   SIMPLE
table         c
type          ref
possible_keys Index_1
key           Index_1
key_len       8
ref           const
rows          1
Extra         

Viel besser, in der Tat wirklich toll.

fügte ich, dass INDEX_1 und meine Löschzeiten im Einklang mit den gemeldeten Zeiten von mattkemp . Das war ein wirklich subtile Fehler meinerseits aufgrund Schuh-Horning einige zusätzliche Funktionen in der letzten Minute. Es stellte sich heraus, dass die meisten der vorgeschlagenen alternativen DELETE / SELECT-Anweisungen, wie Daniel angegeben, im Wesentlichen die gleiche Menge an Zeit nehmen, landete und als soulmerge erwähnt, war die Aussage ziemlich viel das beste, was ich wollte konstruieren können, auf dem, was ich tun musste. Sobald ich einen Index für diese andere Tabelle C zur Verfügung gestellt, meine DELETEs waren schnell.

Postmortem :
Zwei Lektionen gelernt kamen aus dieser Übung. Erstens ist es klar, dass ich nicht die Kraft der Anweisung EXPLAIN nutzen habe eine bessere Vorstellung von den Auswirkungen meiner SQL-Abfragen zu erhalten. Das ist ein Anfängerfehler, so dass ich mich nicht um, dass man gehe verprügeln. Ich werde aus diesem Fehler lernen. Zweitens war der problematische Code das Ergebnis eines ‚get es schnell getan‘Mentalität und unzureichende Design / Prüfung führten zu diesem Problem nicht früher auftauchen. Hätte ich mehrere ansehnliche Testdatensätze zu verwenden, als Testeingang für diese neue Funktionalität erzeugt wird, hätte ich meine Zeit noch nicht verkaufen verschwendet. Meine Tests auf der DB Seite fehlten die Tiefe, die meine Anwendung Seite Platz hat. Jetzt habe ich die Gelegenheit bekommt, dass zu verbessern.

Referenz: EXPLAIN-Anweisung

War es hilfreich?

Lösung

Löschen von Daten aus InnoDB ist die teuerste Operation Sie es anfordern können. Wie Sie bereits die Abfrage entdeckt selbst ist nicht das Problem -. Die meisten von ihnen werden auf jeden Fall auf den gleichen Ausführungsplan optimiert werden

Während es schwierig sein kann, zu verstehen, warum DELETEs aller Fälle die langsamsten sind, gibt es eine ziemlich einfache Erklärung. InnoDB ist eine transaktionale Speicher-Engine. Das bedeutet, dass, wenn Ihre Abfrage halbwegs durch abgebrochen wurde, werden alle Datensätze noch an seinem Platz sein würde, als wäre nichts geschehen. Sobald es abgeschlossen ist, werden alle im selben Augenblick verschwunden sein. Während der DELETE anderen Clients mit dem Server verbunden werden die Datensätze, bis der DELETE abgeschlossen ist zu sehen.

Um dies zu erreichen, InnoDB verwendet eine Technik MVCC (Multi Version Concurrency Control) genannt. Was es im Grunde tut, ist jeder Verbindung eine Momentaufnahme der gesamten Datenbank zu geben, wie es war, als die erste Anweisung der Transaktion gestartet. Um dies zu erreichen, intern jeder Datensatz in InnoDB können mehrere Werte haben - eine für jeden Schnappschuss. Dies ist auch, warum setzt auf InnoDB einige Zeit in Anspruch nimmt -. Es auf dem Snapshot-Zustand ab, die Sie zu dieser Zeit sehen

Für Ihre DELETE Transaktion, jede und jeden Datensatz, der nach Ihren Abfragebedingungen identifiziert wird, wird zum Löschen markiert. Wie andere Clients die Daten zur gleichen Zeit zugreifen könnte, kann es nicht sofort, sie aus der Tabelle entfernen, da sie ihre jeweiligen Snapshot zu gewährleisten die Unteilbarkeit des Löschens müssen sehen.

Wenn alle Datensätze zum Löschen markiert wurden, wird die Transaktion erfolgreich begangen. Und selbst dann können sie nicht sofort von den tatsächlichen Datenseite entfernt werden, bevor alle anderen Transaktionen, die mit einem Snapshot Wert vor Ihrer DELETE Transaktion gearbeitet, haben auch beendet.

So in der Tat Ihre 3 Minuten sind nicht wirklich so langsam, in Anbetracht der Tatsache, dass alle Datensätze, um geändert werden müssen sie in einer Transaktion sichere Weg zur Entfernung vorzubereiten. Wahrscheinlich werden Sie „hören“ Ihre Festplatte arbeiten, während die Anweisung ausgeführt wird. Dies wird verursacht durch alle Zeilen zugreifen. Zur Verbesserung der Leistung können Sie versuchen, InnoDB Pufferpoolgröße für Ihren Server zu erhöhen und versuchen, anderen Zugriff auf die Datenbank zu begrenzen, während Sie DELETE, um dadurch auch die Anzahl der historischen Versionen reduziert InnoDB pro Datensatz zu halten hat. Mit dem zusätzlichen Speicher könnte InnoDB Lage sein, Ihre Tabelle zu lesen (meistens) in dem Speicher und vermeiden Sie Speicherplatz sucht Zeit.

Andere Tipps

Ihre Zeit von drei Minuten scheint wirklich langsam. Meine Vermutung ist, dass die id-Spalte nicht korrekt indiziert. Wenn Sie die genaue Tabellendefinition liefern könnten Sie verwenden, das wäre hilfreich.

habe ich ein einfaches Python-Skript Testdaten zu erzeugen und lief mehrere verschiedene Versionen der Löschabfrage gegen den gleichen Datensatz. Hier ist meine Tabellendefinitionen:

drop table if exists a;
create table a
 (id bigint unsigned  not null primary key,
  data varchar(255) not null) engine=InnoDB;

drop table if exists b;
create table b like a;

I eingefügt dann 100K Zeilen in eine und 25K Zeilen in b (22.5k wiese auch in einem waren). Hier die Ergebnisse der verschiedenen Löschbefehle. Ich fiel und neu besiedelt den Tisch zwischen den Läufen durch die Art und Weise.

mysql> DELETE FROM a WHERE EXISTS (SELECT b.id FROM b WHERE a.id=b.id);
Query OK, 22500 rows affected (1.14 sec)

mysql> DELETE FROM a USING a LEFT JOIN b ON a.id=b.id WHERE b.id IS NOT NULL;
Query OK, 22500 rows affected (0.81 sec)

mysql> DELETE a FROM a INNER JOIN b on a.id=b.id;
Query OK, 22500 rows affected (0.97 sec)

mysql> DELETE QUICK a.* FROM a,b WHERE a.id=b.id;
Query OK, 22500 rows affected (0.81 sec)

Alle Tests wurden auf einem Intel Core2 Quad-Core 2,5 GHz, 2 GB RAM mit Ubuntu 8.10 und MySQL laufen 5.0. Beachten Sie, dass die Ausführung einer SQL-Anweisung ist immer noch single threaded.


Update:

ich meine Tests aktualisiert itsmatt Schema zu verwenden. Ich änderte es leicht durch Entfernen Autoinkrement (I synthetische Daten zu erzeugen bin) und Zeichensatzcodierung (nicht funktionierte - nicht in sie haben zu graben).

Hier ist meine neue Tabellendefinitionen:

drop table if exists a;
drop table if exists b;
drop table if exists c;

create table c (id varchar(30) not null primary key) engine=InnoDB;

create table a (
  id bigint(20) unsigned not null primary key,
  c_id varchar(30) not null,
  h int(10) unsigned default null,
  i longtext,
  j bigint(20) not null,
  k bigint(20) default null,
  l varchar(45) not null,
  m int(10) unsigned default null,
  n varchar(20) default null,
  o bigint(20) not null,
  p tinyint(1) not null,
  key l_idx (l),
  key h_idx (h),
  key m_idx (m),
  key c_id_idx (id, c_id),
  key c_id_fk (c_id),
  constraint c_id_fk foreign key (c_id) references c(id)
) engine=InnoDB row_format=dynamic;

create table b like a;

I reran dann die gleichen Tests mit 100k Zeilen in einem und 25k Zeilen in b (und repopulating zwischen den Läufen).

mysql> DELETE FROM a WHERE EXISTS (SELECT b.id FROM b WHERE a.id=b.id);
Query OK, 22500 rows affected (11.90 sec)

mysql> DELETE FROM a USING a LEFT JOIN b ON a.id=b.id WHERE b.id IS NOT NULL;
Query OK, 22500 rows affected (11.48 sec)

mysql> DELETE a FROM a INNER JOIN b on a.id=b.id;
Query OK, 22500 rows affected (12.21 sec)

mysql> DELETE QUICK a.* FROM a,b WHERE a.id=b.id;
Query OK, 22500 rows affected (12.33 sec)

Wie Sie sehen, das ist ein ziemlich viel langsamer als zuvor, wahrscheinlich aufgrund der vielen Indizes. Es ist jedoch bei weitem nicht die 3 Minuten-Marke.

Noch etwas, das man sich bewegt das Langtext-Feld an das Ende des Schemas, sehen mag. Ich glaube mich zu erinnern, dass mySQL besser abschneidet, wenn alle Größe beschränkt Felder sind in erster Text, Klecks, etc. sind am Ende.

Versuchen Sie folgendes:

DELETE a
FROM a
INNER JOIN b
 on a.id = b.id

Unterabfragen sind in der Regel langsamer sein, dann schließt sie, wie sie für jeden Datensatz in der äußeren Abfrage ausgeführt werden.

Das ist, was ich immer tun, wenn ich mit super großen Daten zu arbeiten habe (hier: eine Probe-Test-Tabelle mit 150000 Zeilen):

drop table if exists employees_bak;
create table employees_bak like employees;
insert into employees_bak 
    select * from employees
    where emp_no > 100000;

rename table employees to employees_todelete;
rename table employees_bak to employees;

In diesem Fall wird die SQL-Filter 50000 Zeilen in die Backup-Tabelle. Die Abfrage Kaskade führt auf meinem langsamen Rechner in 5 Sekunden. Sie können den Einsatz in wählen, indem Sie Ihre eigenen Filterabfrage ersetzen.

Das ist der Trick Masse Löschung auf große Datenbanken auszuführen;! =)

Sie machen Ihre Unterabfrage auf ‚b‘ für jede Zeile in ‚a‘.

Versuchen:

DELETE FROM a USING a LEFT JOIN b ON a.id = b.id WHERE b.id IS NOT NULL;

Probieren Sie dies aus:

DELETE QUICK A.* FROM A,B WHERE A.ID=B.ID

Es ist viel schneller als die normalen Abfragen.

Siehe für Syntax: http://dev.mysql.com /doc/refman/5.0/en/delete.html

Ich weiß, diese Frage ziemlich aufgrund OPs Indizierung Auslassungen gelöst ist, aber ich mag diese zusätzliche Beratung bieten, die für einen allgemeineren Fall dieses Problems gilt.

Ich habe persönlich mit, die löschen viele Zeilen aus einer Tabelle behandelt, die in einem anderen existieren und in meiner Erfahrung ist es am besten die folgenden zu tun, vor allem, wenn Sie viele Zeilen erwarten gelöscht werden. Diese Technik wird vor allem Replikations-Slave-Verzögerung verbessern, da die mehr jede einzelne Mutator Abfrage ausgeführt wird, desto schlimmer würde die Verzögerung sein (Replikation ist single threaded).

So, hier ist es: tun, um eine SELECT zunächst als separate Abfrage , die Erinnerung an der IDs zurück in Ihrem Skript / Anwendung, dann weiter in den Reihen auf Löschen (etwa 50.000 Zeilen gleichzeitig ). Dies wird erreicht wie folgt vor:

  • jeder der Delete-Anweisungen wird die Tabelle nicht zu lange sperren, damit nicht die Replikation Verzögerung läßt außer Kontrolle bekommen . Es ist besonders wichtig, wenn Sie auf Ihrer Replikation verlassen Sie relativ up-to-date Daten zu liefern. Der Vorteil Chargen besteht darin, dass, wenn Sie feststellen, dass jede DELETE Abfrage immer noch zu lange dauert, können Sie es anpassen können kleiner sein, ohne DB-Strukturen zu berühren.
  • ein weiterer Vorteil eine separate SELECT zu verwenden, ist, dass die SELECT selbst könnte eine lange Zeit in Anspruch nehmen laufen, vor allem, wenn es kann nicht aus irgendeinem Grunde den besten DB-Indizes verwenden. Wenn die SELECT auf eine DELETE Innen ist, wenn die gesamte Anweisung an die Slaves wandert, wird es die SELECT noch einmal tun, was möglicherweise die Sklaven hinken, weil es wieder zu tun hat, wählen Sie die lange am ganzen Körper. Slave Verzögerung, wieder leidet schlecht. Wenn Sie eine separate SELECT-Abfrage verwenden, dieses Problem geht weg, da alle vorbei sind Sie eine Liste von IDs ist.

Lassen Sie mich wissen, ob es ein Fehler in meiner Logik irgendwo.

Für weitere Diskussion über die Replikation Verzögerung und Wege, sie zu bekämpfen, ähnlich wie diese finden Sie unter MySQL Slave Lag (Verzögerung) erklärt und 7 Wege kämpfen es

P. S. Eine Sache vorsichtig zu sein, ist natürlich, mögliche Änderungen an der Tabelle zwischen den Zeiten, die SELECT-Oberflächen und DELETEs starten. Ich werde Sie solche Details behandeln von Transaktionen und / oder Logik relevant für Ihre Anwendung.

DELETE FROM a WHERE id IN (SELECT id FROM b)

Vielleicht sollten Sie die indicies wieder aufzubauen, bevor eine solche hugh Abfrage ausgeführt wird. Nun, Sie sollten sie in regelmäßigen Abständen neu zu erstellen.

REPAIR TABLE a QUICK;
REPAIR TABLE b QUICK;

und dann eine der oben genannten Abfragen ausführen (d.)

DELETE FROM a WHERE id IN (SELECT id FROM b)

Die Abfrage selbst bereits in einer optimalen Form ist, die Indizes Aktualisierung bewirkt, dass der gesamte Vorgang so lange dauern. Sie könnten die Schlüssel deaktivieren auf die Tabelle vor dem Betrieb, dass die Dinge beschleunigen soll. Sie können sie wieder aktivieren zu einem späteren Zeitpunkt, wenn Sie sie nicht sofort benötigen.

Ein anderer Ansatz wäre eine deleted Flag-Spalte zu Ihrer Tabelle des Hinzufügen und Anpassen andere Abfragen, so dass sie diesen Wert zu berücksichtigen. Der schnellste Typ boolean in mysql ist CHAR(0) NULL (true = '', false = NULL). Das wäre eine schnelle Operation sein, die Werte löschen können danach.

Die gleichen Gedanken in SQL-Anweisungen ausgedrückt:

ALTER TABLE a ADD COLUMN deleted CHAR(0) NULL DEFAULT NULL;

-- The following query should be faster than the delete statement:
UPDATE a INNER JOIN b SET a.deleted = '';

-- This is the catch, you need to alter the rest
-- of your queries to take the new column into account:
SELECT * FROM a WHERE deleted IS NULL;

-- You can then issue the following queries in a cronjob
-- to clean up the tables:
DELETE FROM a WHERE deleted IS NOT NULL;

Wenn das auch nicht, was Sie wollen, können Sie einen Blick auf das, was die mysql docs haben über die Geschwindigkeit von Lösch Aussagen .

BTW, nachdem die oben auf meinem Blog veröffentlichen, Baron Schwartz von Percona gebracht, um meine Aufmerksamkeit, dass sein Maatkit hat bereits ein Werkzeug für diesen Zweck - mk-Archivierungs. http://www.maatkit.org/doc/mk-archiver.html.

Es ist sehr wahrscheinlich die beste Werkzeug für den Job.

Offensichtlich ist die SELECT Abfrage, die die Grundlage für Ihren DELETE Betrieb baut recht schnell ist, so würde ich denken, dass entweder der Fremdschlüssel oder die Indizes sind die Gründe für Ihre extrem langsame Abfrage.

Versuchen

SET foreign_key_checks = 0;
/* ... your query ... */
SET foreign_key_checks = 1;

Dies würde die Kontrolle des Fremdschlüssel deaktivieren. Leider können Sie nicht deaktivieren (zumindest ich weiß nicht, wie) den Schlüssel Updates mit einer InnoDB-Tabelle. Mit einer MyISAM-Tabelle können Sie etwas tun, wie

ALTER TABLE a DISABLE KEYS
/* ... your query ... */
ALTER TABLE a ENABLE KEYS 

ich eigentlich nicht testen, ob diese Einstellungen die Abfrage Dauer auswirken würden. Aber es ist ein Versuch wert.

Eine Verbindung Datebase Terminal und führen Sie folgenden Befehl, schau dir das Ergebnis Zeit jeder von ihnen, werden Sie feststellen, dass die Zeiten der Lösch- 10, 100, 1000, 10000, 100000 Datensätze werden nicht vervielfacht.

  DELETE FROM #{$table_name} WHERE id < 10;
  DELETE FROM #{$table_name} WHERE id < 100;
  DELETE FROM #{$table_name} WHERE id < 1000;
  DELETE FROM #{$table_name} WHERE id < 10000;
  DELETE FROM #{$table_name} WHERE id < 100000;

Die Zeit von 10 Tausend Löschen von Datensätzen ist nicht mehr als 10-mal so viel wie 100 Tausend Datensätze zu löschen. Dann, mit Ausnahme der Suche nach einer Möglichkeit, Einträge löschen mehr schneller, gibt es einige indirekten Methoden.

1, können wir die table_name umbenennen table_name_bak, und wählen Sie dann Datensätze aus table_name_bak table_name.

2, 10000 Datensätze zu löschen, wir 1000 Datensätze 10 mal löschen. Es ist ein Beispiel Ruby-Skript, es zu tun.

#!/usr/bin/env ruby
require 'mysql2'


$client = Mysql2::Client.new(
  :as => :array,
  :host => '10.0.0.250',
  :username => 'mysql',
  :password => '123456',
  :database => 'test'
)


$ids = (1..1000000).to_a
$table_name = "test"

until $ids.empty?
  ids = $ids.shift(1000).join(", ")
  puts "delete =================="
  $client.query("
                DELETE FROM #{$table_name}
                WHERE id IN ( #{ids} )
                ")
end

Die grundlegende Technik für mehrreihige Form MySQL in einzelner Tabelle durch das ID-Feld zu löschen

DELETE FROM tbl_name WHERE id <= 100 AND id >=200; Diese Abfrage ist verantwortlich für den angepaßten Zustand zwischen 100 und 200 aus der bestimmten Tabelle zu löschen

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top