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.
quelle
Antworten:
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
Stellen Sie sich die Sequenz in Ereignissen vor
Fragen aus Frage 2
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]).
quelle
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?
quelle