2009-02-04 4 views
34

Ich versuche, herauszufinden, wie man eine sehr langsame Abfrage in MySQL zu optimieren (Ich habe das nicht Entwurf):„SELECT COUNT (*)“ ist langsam, sogar mit where-Klausel

SELECT COUNT(*) FROM change_event me WHERE change_event_id > '1212281603783391'; 
+----------+ 
| COUNT(*) | 
+----------+ 
| 3224022 | 
+----------+ 
1 row in set (1 min 0.16 sec) 

Vergleich dass auf eine volle Zahl:

select count(*) from change_event; 
+----------+ 
| count(*) | 
+----------+ 
| 6069102 | 
+----------+ 
1 row in set (4.21 sec) 

Die Aussage erklärt mir nicht hier helfen:

explain SELECT COUNT(*) FROM change_event me WHERE change_event_id > '1212281603783391'\G 
*************************** 1. row *************************** 
      id: 1 
    select_type: SIMPLE 
     table: me 
     type: range 
possible_keys: PRIMARY 
      key: PRIMARY 
     key_len: 8 
      ref: NULL 
     rows: 4120213 
     Extra: Using where; Using index 
1 row in set (0.00 sec) 

OK, es immer noch denkt, dass es etwa 4 Millionen Einträge zu zählen braucht, bu t Ich könnte Zeilen in einer Datei schneller zählen als das! Ich verstehe nicht, warum MySQL so lange braucht.

Hier ist die Tabellendefinition:

CREATE TABLE `change_event` (
    `change_event_id` bigint(20) NOT NULL default '0', 
    `timestamp` datetime NOT NULL, 
    `change_type` enum('create','update','delete','noop') default NULL, 
    `changed_object_type` enum('Brand','Broadcast','Episode','OnDemand') NOT NULL, 
    `changed_object_id` varchar(255) default NULL, 
    `changed_object_modified` datetime NOT NULL default '1000-01-01 00:00:00', 
    `modified` datetime NOT NULL default '1000-01-01 00:00:00', 
    `created` datetime NOT NULL default '1000-01-01 00:00:00', 
    `pid` char(15) default NULL, 
    `episode_pid` char(15) default NULL, 
    `import_id` int(11) NOT NULL, 
    `status` enum('success','failure') NOT NULL, 
    `xml_diff` text, 
    `node_digest` char(32) default NULL, 
    PRIMARY KEY (`change_event_id`), 
    KEY `idx_change_events_changed_object_id` (`changed_object_id`), 
    KEY `idx_change_events_episode_pid` (`episode_pid`), 
    KEY `fk_import_id` (`import_id`), 
    KEY `idx_change_event_timestamp_ce_id` (`timestamp`,`change_event_id`), 
    KEY `idx_change_event_status` (`status`), 
    CONSTRAINT `fk_change_event_import` FOREIGN KEY (`import_id`) REFERENCES `import` (`import_id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 

Version:

$ mysql --version 
mysql Ver 14.12 Distrib 5.0.37, for pc-solaris2.8 (i386) using readline 5.0 

Gibt es etwas offensichtlich, dass ich vermisst habe? (Ja, ich habe bereits versucht "SELECT COUNT (change_event_id)", aber es gibt keinen Leistungsunterschied).

+0

Wie wäre es, wenn Sie etwas wie ... versuchen SELECT COUNT (*) FROM change_event mich WHERE change_event_id> 0; Beeinflusst es die Leistung? –

+0

Ovid - wenn Sie in der Lage sind, fügen Sie bitte die Ausgabe von 'SHOW INDEX FROM change_event' – Alnitak

Antwort

39

InnoDB gruppierten Primärschlüssel verwendet, so dass der Primärschlüssel zusammen mit der Reihe in den Datenseite, nicht in separaten Indexseite gespeichert ist. Um einen Bereichsscan durchzuführen, müssen Sie immer noch alle potenziell breiten Zeilen auf Datenseiten durchsuchen. Beachten Sie, dass diese Tabelle eine TEXT-Spalte enthält.

Zwei Dinge, die ich würde versuchen:

  1. Lauf optimize table. Dadurch wird sichergestellt, dass die Datenseiten in sortierter Reihenfolge physisch gespeichert werden. Dies könnte möglicherweise einen Bereichsscan auf einem gruppierten Primärschlüssel beschleunigen.
  2. Erstellen Sie einen zusätzlichen nicht-primären Index nur für die Spalte change_event_id. Dadurch wird eine Kopie dieser Spalte in Indexseiten gespeichert, die viel schneller gescannt werden können. Überprüfen Sie nach dem Erstellen des Plans den EXPLAIN-Plan, um sicherzustellen, dass er den neuen Index verwendet.

(Sie wollen wahrscheinlich die change_event_id Spalte Bigint unsigned machen, wenn es von Null erhöht wird ist)

+5

Die "Optimierungstabelle" hat nicht viel geholfen, aber der redundante Index hat das gelöst Problem. Vielen Dank! – Ovid

+12

Dies ist das erste Mal, dass ich jemanden gesehen habe, der vorgeschlagen hat, einen redundanten Index auf einer PRIMARY KEY-Spalte als Performance-Hack in MySQL zu erstellen. Ich bin sehr interessiert an den Details, warum dies funktioniert und an den Arten von Abfragen, für die es nützlich ist. Haben Sie Links zu weiteren Themen? –

+0

'OPTIMIZE TABLE' wird nur selten verwendet, besonders in InnoDB-Tabellen. Eine Verbesserung könnte sein, weil Sie die gesamte Tabelle frisch in den Cache geladen haben. –

1

Führen Sie "analyze table_name" auf dieser Tabelle - es ist möglich, dass die Indizes nicht mehr optimal sind.

Sie können dies oft sagen, indem Sie "show index from table_name" ausführen. Wenn der Kardinalitätswert NULL ist, müssen Sie eine erneute Analyse erzwingen.

+0

"Analyse Tabelle change_event" hatte keinen Einfluss auf die Leistung. Trotzdem danke. – Ovid

+0

hat es die Ebene "Select Count (*)" schneller gemacht? Ich habe gerade einen 110M Rekord MyISAM Tisch versucht. "Auswahl zählen (*)" war sofort.Die Auswahl für die Hälfte der Tabelle dauerte 2m48 beim ersten Mal und 27s beim zweiten Mal. – Alnitak

+2

MyISAM hat radikal andere Leistungsmerkmale als InnoDB. Das liegt daran, dass MyISAM Sperren auf Tabellenebene durchführt und effektiv nur jeweils eine Transaktion hat. InnoDB verhält sich unter den Deckeln sehr unterschiedlich. – Ovid

3

Überprüfen Sie, wie fragmentiert Ihre Indizes sind. In meiner Firma haben wir einen nächtlichen Importprozess, der unsere Indizes zerstört und im Laufe der Zeit die Datenzugriffsgeschwindigkeit stark beeinflussen kann. Zum Beispiel hatten wir eine SQL-Prozedur, die 2 Stunden dauerte, um einen Tag nach der Defragmentierung der Indizes 3 Minuten zu laufen. Wir verwenden SQL Server 2005 und suchen nach einem Skript, das dies auf MySQL überprüfen kann.

Update: Schauen Sie sich den folgenden Link: http://dev.mysql.com/doc/refman/5.0/en/innodb-file-defragmenting.html

+0

Hier ist ein Link http://dev.mysql.com/doc/refman/5.0/en/innodb-file-defragmenting.html viel Glück mit allem –

+0

Sie möchten diesen Link in Ihrer Antwort setzen? – MiniQuark

5

Ich habe vor mit IP-Geolocation-Datenbanken wie dies in Verhalten führen. Nach einer Reihe von Datensätzen scheint sich die Fähigkeit von MySQL, aus Indizes für bereichsbasierte Abfragen Vorteile zu ziehen, offensichtlich zu verflüchtigen. Mit den Geolocation-DBs haben wir es gehandhabt, indem wir die Daten in Chunks segmentiert haben, die vernünftig genug waren, um die Verwendung der Indizes zu ermöglichen.

+0

Was für eine böse Lösung. Nichtsdestoweniger, ich habe es früher erwähnt und ohne eine seltsame Konfiguration oder andere Lösung, wir könnten gezwungen werden, diese Route zu gehen :( – Ovid

+0

Dies ist eine großartige Lösung, die ein Grundprinzip von Computer-Lösungen respektiert: Programmierung in der großen ist qualitativ Im Fall von Datenbanken ändern sich die Zugriffspläne und die Verwendung von Indizes dramatisch, wenn die Größe über bestimmte Schwellenwerte hinaus steigt. " –

+0

Ich stieß auf ein ähnliches Problem mit der Geolocation-Datenbank und nach verschiedenen Optimierungsversuchen wie der Indizierung Ich habe gerade versucht, große Tabellen in kleinere Datasets zu unterteilen, was sich letztendlich in Bezug auf die Performance als akzeptabel erwies. – shashi009

0

Ich würde eine "Counters" -Tabelle erstellen und "create row"/"delete row" -Trigger zu der Tabelle hinzufügen, die Sie zählen. Die Trigger sollten Zählwerte in der Tabelle "counters" bei jedem Einfügen/Löschen erhöhen/verringern, sodass Sie sie nicht jedes Mal neu berechnen müssen, wenn Sie sie benötigen.

Sie können dies auch auf der Anwendungsseite erreichen, indem Sie die Zähler zwischenspeichern, aber das bedeutet, dass der "Zähler-Cache" bei jedem Einfügen/Löschen gelöscht wird.

Für einige Referenz einen Blick auf diesem http://pure.rednoize.com/2007/04/03/mysql-performance-use-counter-tables/

+0

Außer dass wir Zählungen für Bereiche benötigen, funktioniert eine Zählung über Trigger nicht (außer ich habe dich missverstanden) – Ovid

14

Hier sind ein paar Dinge, die ich vorschlagen:

  • Ändern Sie die Spalte von a "bigint" zu "int unsigned". Erwarten Sie wirklich, mehr als 4,2 Milliarden Datensätze in dieser Tabelle zu haben? Wenn nicht, dann verschwenden Sie Platz (und Zeit) das extra-weite Feld. MySQL-Indizes sind bei kleineren Datentypen effizienter.

  • Führen Sie den Befehl "OPTIMIZE TABLE", und sehen Sie, ob Ihre Abfrage später schneller ist.

  • Sie könnten auch partitioning your table gemäß dem ID-Feld berücksichtigen, insbesondere wenn ältere Datensätze (mit niedrigeren ID-Werten) im Laufe der Zeit weniger relevant werden. Eine partitionierte Tabelle kann häufig Aggregatabfragen schneller ausführen als eine große, nicht partitionierte Tabelle.


EDIT:

Bei näherem Hinsehen an diesem Tisch, es sieht aus wie eine Logging-Stil Tabelle, wo Zeilen eingefügt werden, aber nie geändert.

Wenn das stimmt, benötigen Sie möglicherweise nicht die gesamte Transaktionssicherheit, die von der InnoDB-Speicher-Engine bereitgestellt wird, und Sie können möglicherweise mit switching to MyISAM durchkommen, was bei Sammelabfragen erheblich effizienter ist.

+1

Da wir Zahlen wie "1212281603783397" haben, denke ich, dass bereits "int unsigned" überläuft (es ist ein hochauflösender Zeitstempel). „OPTIMIZE TABLE“ hatte keine Auswirkungen auf die Leistung :( Ist das nicht MyISAM viel langsamer mit „wo“ Klauseln, da es sich um eine Tabelle zu tun, muss scannen? Außerdem würde wir unsere FK Einschränkung verlieren. – Ovid

+0

Warum für einen Zeitstempel verwenden Ihr Primärschlüssel, wenn Sie bereits ein Zeitstempelfeld haben? Was passiert auch, wenn zwei Ereignisse gleichzeitig passieren? Wenn ich Sie wäre, würde ich ein einfaches Autoinkrementfeld für die pKey verwenden. – benjismith

+0

Die WHERE-Klausel funktioniert nicht. t Für eine einfache Abfrage (equals, less-than, great-than usw.) für eine indizierte Spalte verwendet der Abfrageoptimierer den Index, um relevante Seiten zu finden, und scannt nur diese Seiten wäre erforderlich, wenn Sie date-math oder substrings machen würden – benjismith

1

MySQL sagt zuerst "Wo verwenden", da es alle Datensätze/Werte aus den Indexdaten lesen muss, um sie tatsächlich zu zählen. Mit InnoDb versucht es auch, die 4-Mil-Record-Range "zu packen", um sie zu zählen.

Sie müssen möglicherweise mit unterschiedlichen Transaktionsisolationsstufen experimentieren: http://dev.mysql.com/doc/refman/5.1/en/set-transaction.html#isolevel_read-uncommitted

und sehen, welche besser ist.

Mit MyISAM wäre es nur schnell, aber mit intensiven Schreib-Modell wird Lock-Probleme führen.