Gründe für gelegentlich langsame Abfragen?


16

Wir führen MySQL 5.1 unter Windows Server 2008 R2 aus.

Wir haben in letzter Zeit einige Diagnosen in unserer Datenbank durchgeführt und einige störende Artefakte gefunden, die wir nicht erklären können . Wir haben Code zur Protokollierung hinzugefügt, wenn Abfragen lange gedauert haben (> 2000 ms). Die Ergebnisse waren überraschend (und möglicherweise eine Erklärung für unsere Deadlocks).

Gelegentlich dauern Abfragen, die normalerweise sehr wenig Zeit in Anspruch nehmen (<10 ms), 4 bis 13 Sekunden. Es handelt sich hierbei um Abfragen, die ständig (mehrmals pro Sekunde) ausgeführt werden und nicht unter diesen Abfragezeitspitzen leiden.

Wir haben unsere Indizes nach offensichtlichen Fehlern durchsucht und hatten nicht viel Glück.

Aktualisieren

Der Volkstisch:

| people | CREATE TABLE `people` (
`people_id` bigint(20) NOT NULL AUTO_INCREMENT,
`company_id` bigint(20) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`temp_password` varchar(10) DEFAULT NULL,
`reset_password_hash` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`phone` varchar(32) DEFAULT NULL,
`mobile` varchar(32) DEFAULT NULL,
`iphone_device_id` varchar(160) DEFAULT NULL,
`iphone_device_time` datetime DEFAULT NULL,
`last_checkin` datetime DEFAULT NULL,
`location_lat` double DEFAULT NULL,
`location_long` double DEFAULT NULL,
`gps_strength` smallint(6) DEFAULT NULL,
`picture_blob_id` bigint(20) DEFAULT NULL,
`authority` int(11) NOT NULL DEFAULT '0',
`active` tinyint(1) NOT NULL DEFAULT '1',
`date_created` datetime NOT NULL,
`last_login` datetime NOT NULL,
`panic_mode` tinyint(1) NOT NULL DEFAULT '0',
`battery_level` double DEFAULT NULL,
`battery_state` varchar(32) DEFAULT NULL,
PRIMARY KEY (`people_id`),
KEY `email` (`email`),
KEY `company_id` (`company_id`),
KEY `iphone_device_id` (`iphone_device_id`),
KEY `picture_blob_id` (`picture_blob_id`),
CONSTRAINT `people_ibfk_1` FOREIGN KEY (`company_id`) REFERENCES `companies` (`company_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `people_ibfk_2` FOREIGN KEY (`picture_blob_id`) REFERENCES `blobs` (`blob_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4658 DEFAULT CHARSET=utf8 |

Indizes:

+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| Table  | Non_unique | Key_name         | Seq_in_index | Column_name      | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| people |          0 | PRIMARY          |            1 | people_id        | A         |        3502 |     NULL | NULL   |      | BTREE      |         |
| people |          1 | email            |            1 | email            | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
| people |          1 | company_id       |            1 | company_id       | A         |        3502 |     NULL | NULL   |      | BTREE      |         |
| people |          1 | iphone_device_id |            1 | iphone_device_id | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
| people |          1 | picture_blob_id  |            1 | picture_blob_id  | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+

Die Tabelle auf dem Server enthält ca. 5000 Zeilen, die uns Probleme bereiten.


1
Es gibt etwas, das Sie in den beiden vorhergehenden Fragen noch nicht gezeigt haben. Bitte fügen Sie dieser Frage drei (3) Dinge hinzu: 1) SHOW CREATE TABLE people \ G 2) SHOW INDEXES FROM people; 3) COUNT AUSWÄHLEN (1) VON Personen;
RolandoMySQLDBA

@RolandoMySQLDBA Das mache ich, sobald ich morgen zur Arbeit komme. Prost :)
RedBlueThing

Ich habe meine Antwort aktualisiert. Bitte lesen Sie !!!
RolandoMySQLDBA

@ RolandoMySQLDBA Danke :). Parsing immer noch dieses Zeug. Ich werde dich wissen lassen, wie wir gehen.
RedBlueThing

Antworten:


14

Die UPDATE-Abfragen in Ihren beiden vorherigen Fragen ( Frage 1 , Frage 2 ) treffen die Tabelle 'people' von PRIMARY KEY mit Sperrung auf Zeilenebene. Dies habe ich bereits in Frage 1 am 6. Juni 2011 um 10:03 Uhr festgestellt

Alle Transaktionen durchlaufen den PRIMARY-Schlüssel. Da der PRIMARY ein Clustered-Index in InnoDB ist, sind der PRIMARY-Schlüssel und die Zeile selbst zusammen. Durchqueren einer Reihe und und der PRIMARY KEY sind also ein und dasselbe. Daher ist jede Indexsperre für den PRIMARY KEY auch eine Sperre auf Zeilenebene.

Noch wurde nicht an etwas anderes gedacht, das den Indizes Langsamkeit zuschreiben kann: Die Verwendung von NON-UNIQUE-Indizes in InnoDB. Bei jeder indizierten Suche in InnoDB mit nicht eindeutigen Indizes ist auch die Zeilen-ID jeder Zeile mit dem nicht eindeutigen Schlüssel verknüpft. Die rowID stammt im Wesentlichen aus dem Clustered Index . Aktualisieren von nicht eindeutigen Indizes MUSS IMMER mit dem Clustered-Index interagieren, AUCH WENN DIE TABELLE KEINEN PRIMÄREN SCHLÜSSEL HAT.

Eine weitere Überlegung ist das Verwalten von BTREE-Knoten in einem Index. Manchmal ist die Seitenteilung von Knoten erforderlich. Alle Einträge im BTREE-Knoten nicht eindeutiger Indizes enthalten nicht eindeutige Felder zuzüglich der Zeilen-ID im Clustered-Index. Um die Aufteilung solcher BTREE-Seiten ordnungsgemäß zu verringern, ohne die Datenintegrität zu beeinträchtigen, muss die der rowID zugeordnete Zeile intern eine Sperre auf Zeilenebene aufweisen.

Wenn die 'people'-Tabelle viele nicht eindeutige Indizes enthält, bereiten Sie sich darauf vor, eine große Anzahl von Indexseiten im Tablespace zu haben und von Zeit zu Zeit winzige kleine Zeilensperren auf Sie zukommen zu lassen.

Es gibt noch einen anderen Faktor, der nicht so offensichtlich ist: die Schlüsselbevölkerung

Wenn ein Index gefüllt wird, können die Schlüsselwerte, aus denen sich die Indizes zusammensetzen, mit der Zeit schief werden und dazu führen, dass der MySQL Query Optimizer von Schlüsselsuchen zu Indexprüfungen und schließlich zu vollständigen Tabellensuchen wechselt. Dies können Sie nur steuern, wenn Sie die Tabelle mit neuen Indizes neu entwerfen, um die einseitige Ausrichtung der Schlüssel zu kompensieren. Bitte geben Sie die Tabellenstruktur für die 'people'-Tabelle, die Anzahl der' people'-Tabellen und die Ausgabe der show-Indizes für die 'people'-Tabelle an .

Selbst wenn Abfragen nur den PRIMARY KEY verwenden, muss für die Einseitigkeit von Schlüsseln in nicht eindeutigen Indizes noch ein BTREE-Ausgleich und eine Seitenteilung erfolgen. Eine solche BTREE-Verwaltung führt zu einer merklichen Verlangsamung aufgrund von zeitweiligen Sperren auf Zeilenebene, die Sie nicht beabsichtigt hatten.

UPDATE 2011-06-14 22:19

Fragen aus Frage 1

UPDATE people SET company_id = 1610, name = '<name>', password = '<hash>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@yahoo.com',
phone = NULL, mobile = '<phone>', iphone_device_id = 'android:<id>-<id>',
iphone_device_time = '2011-06-06 05:35:09', last_checkin = '2011-06-06 05:24:42',
location_lat = <lat>, location_long = -<lng>, gps_strength = 3296,
picture_blob_id = 1190,
authority = 1, active = 1, date_created = '2011-04-13 20:21:20',
last_login = '2011-06-06 05:35:09', panic_mode = 0,
battery_level = NULL, battery_state = NULL WHERE people_id = 3125

UPDATE people SET company_id = 1610, name = '<name>', password = '<hash>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@yahoo.com',
phone = NULL, mobile = '<phone>', iphone_device_id = 'android:<id>-<id>-<id>-<id>',
iphone_device_time = '2011-06-06 05:24:42', last_checkin = '2011-06-06 05:35:07',
location_lat = <lat>, location_long = -<lng>, gps_strength = 3296,
picture_blob_id = 1190,
authority = 1, active = 1, date_created = '2011-04-13 20:21:20',
last_login = '2011-06-06 05:35:09', panic_mode = 0,
battery_level = NULL, battery_state = NULL WHERE people_id = 3125

Stellen Sie sich die Sequenz in Ereignissen vor

  1. Finde die Reihe mit dem PRIMARY KEY
  2. Sperren Sie die Zeile und den Clustered-Index
  3. Erstellen Sie MVCC-Daten für alle zu aktualisierenden Spalten
  4. Vier Spalten sind indiziert (email, company_id, iphone_device_id, picture_blob_id)
  5. Jeder Index erfordert eine BTREE-Verwaltung
  6. Innerhalb desselben Transaktionsbereichs wird versucht, die Schritte 1-5 in derselben Zeile zu wiederholen und dieselben Spalten zu aktualisieren (E-Mail in beiden Abfragen gleich, Unternehmens_ID in beiden Abfragen gleich, Bild_Blob_ID in beiden Abfragen gleich, iphone_Device_ID unterschiedlich).

Fragen aus Frage 2

UPDATE people SET iphone_device_id=NULL
WHERE iphone_device_id='iphone:<device_id_blah>' AND people_id<>666;

UPDATE people SET company_id = 444, name = 'Dad', password = '<pass>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@gmail.com',
phone = NULL, mobile = NULL, iphone_device_id = 'iphone:<device_id_blah>',
iphone_device_time = '2011-06-06 19:12:29', last_checkin = '2011-06-07 02:49:47',
location_lat = <lat>, location_long = <lng>, gps_strength = 66,
picture_blob_id = 1661,
authority = 1, active = 1, date_created = '2011-03-20 19:18:34',
last_login = '2011-06-07 11:15:01', panic_mode = 0, battery_level = 0.55,
battery_state = 'unplugged' WHERE people_id = 666;

Diese beiden Abfragen sind noch verwirrender, da mit der ersten Abfrage alles außer people_id 666 aktualisiert wird. Hunderte von Zeilen werden nur mit der ersten Abfrage schmerzhaft gesperrt. Die zweite Abfrage aktualisiert die people_id 666, die die Sequenz von Ereignissen ausführt. Bei der ersten Abfrage werden mit Ausnahme von people_id 666 für jede beteiligte Zeile die gleichen 5 Ereignissequenzen ausgeführt, aber der Index für iphone_device_id befindet sich auf einem interecept-Kurs mit zwei verschiedenen Abfragen. Jemand muss die BTREE-Seiten nach dem Prinzip "Wer zuerst kommt, mahlt zuerst" sperren.

Angesichts dieser beiden Fragenpaare in einem Kollisionskurs kann es für InnoDB oder jedes ACID-konforme RDBMS schwierig sein, möglicherweise dieselben BTREE-Seiten innerhalb eines Index zu sperren. Daher ist eine Indexverlangsamung das Schicksal dieser Abfragepaare, sofern Sie nicht garantieren können, dass die Abfragen mit AUTOCOMMIT = 1 ausgeführt werden, oder indem Sie Dirty Reads zulassen (obwohl solche Kollisionen READ-COMMITTED und READ-UNCOMMITED zu einem Albtraum für MVCC machen).

UPDATE 15.06.2011 10:29

@RedBlueThing: In den Abfragen von Frage 2 ist die erste Abfrage eine Bereichsabfrage, daher werden viele Zeilensperren erreicht. Beachten Sie auch, dass beide Abfragen versuchen, den gleichen Speicherplatz zu sperren. ID 0 Seite Nr. 4611 n Bits 152 werden im PRIMARY KEY, auch bekannt als Clustered Index, gesperrt.

Um sicherzustellen, dass Ihre App basierend auf den von Ihnen erwarteten Ereignissen ausgeführt wird, gibt es zwei verschiedene Möglichkeiten:

Option 1) Konvertieren Sie diese Tabelle in MyISAM (zumindest auf einem Entwicklungsserver). Bei jedem UPDATE, INSERT und DELETE wird eine vollständige Tabellensperre auf der Basis von "first come first serve" festgelegt.

Option 2) Verwenden Sie die Isolationsstufe SERIALIZABLE . Dadurch werden alle beabsichtigten Zeilen im SHARED-Modus gesperrt.

Die erwartete Ereignissequenz wird mit diesen beiden alternativen Optionen unterbrochen oder erfolgreich ausgeführt. Wenn beide Optionen fehlschlagen, müssen Sie Ihre App überprüfen und die Reihenfolge der Ausführung Ihrer Abfragen priorisieren. Sobald Sie diese Priorität festgelegt haben, können Sie diese Optionen einfach rückgängig machen (für Option 1 kehren Sie zu InnoDB zurück, für Option 2 kehren Sie zur Standardisolationsstufe zurück [Verwendung von SERIALIZABLE beenden]).


@RolandoMySQLDBA Ich habe unsere Frage mit den Details aktualisiert, nach denen Sie gefragt haben.
RedBlueThing

@RolandoMySQLDBA Vielen Dank, dass Sie sich das noch einmal angesehen haben. Ich frage mich, Sie Kommentare zu Frage 2, warum die erste Abfrage Hunderte von Zeilen sperren würde? Würde es nicht nur nicht 666 Zeilen sperren, die mit der Geräte-ID übereinstimmen? (dh eine einzelne Zeile)
RedBlueThing

@RolandoMySQLDBA Auf der Grundlage Ihres Vorschlags aus Frage 1 haben wir unsere Autocommit-Einstellung überprüft und bestätigt, dass sie aktiviert ist.
RedBlueThing

@RolandoMySQLDBA Gibt es ein bestimmtes Problem mit den Abfragen aus der ersten Frage (abgesehen von der Aktualisierung aller Felder in der Zeile). Etwas, das eine Ausführungszeit von 13 Sekunden für die Abfrage erklären würde? Ich habe das Gefühl, dass Sie die Indizierung von vier Spalten nicht empfehlen würden, aber würde dies wirklich zu einer so schlechten Leistung führen?
RedBlueThing

@RolandoMySQLDBA +1 und vielen Dank für alle Ihre Vorschläge. Wir haben nicht die Isolationsstufe geändert, um das Problem zu beheben. Stattdessen haben wir Teilfeldaktualisierungen für Frage 2 durchgeführt und eine Abfrage im Aktualisierungspfad entfernt. Voila! keine Deadlocks mehr. :)
RedBlueThing

3

SHOW VARIABLES LIKE 'innodb%'; - Insbesondere wenn die Daten und Indizes gerade nicht die Größe des Pufferpools erreicht haben, könnten Sie die Festplatte viel härter treffen als zuvor. I / O ist der große Leistungskiller.

Die meisten Ihrer Felder sind doppelt so groß wie nötig. BIGINT (8 Bytes) ist für die meisten IDs viel zu viel. 5000 Zeilen benötigen nur SMALLINT UNSIGNED (maximal 65 KB, nur 2 Byte). Oder verwenden Sie MEDIUMINT für eine gewisse Sicherheit.

DOUBLE gibt Ihnen 16 signifikante Stellen zu einem Preis von 8 Bytes. Hat battery_level mehr als 2 signifikante Präzisionsziffern? FLOAT benötigt 4 Bytes.

Mein Punkt hier ist, dass "kleiner -> cachefähiger -> schneller".

Bitte zeigen Sie uns die langsamen Fragen; Zumindest einige von denen, die plötzlich langsamer geworden sind. Ohne sie können wir nur Vermutungen anstellen. Schalten Sie das Slowlog ein und setzen Sie long_query_time = 1; Diese helfen, die langsamsten Abfragen zu finden.

Verstehen Sie den Nutzen von "zusammengesetzten" Indizes?

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.