Warum sollte MySQL serielle synchrone E / A ausführen?

8

Bei der Betrachtung einer besonders ärgerlichen Abfrage über MyISAM-Tabellen, deren Ausführung mehrmals lange dauert, habe ich festgestellt, dass MySQL ein ziemlich seltsames E / A-Muster aufzudecken scheint: Wenn eine einzelne Abfrage ausgeführt wird und eine signifikante Abfrage ausgeführt werden muss E / A-Menge (z. B. für einen Tabellenscan oder wenn die Caches leer sind, echo 3 > /proc/sys/vm/drop_cachessodass die Indizes zuerst von der Festplatte geladen werden müssen), liegt die Warteschlangengröße für das zugrunde liegende Blockgerät nahe dem Wert 1 mit einer miserablen Leistung von nur 4-5 MB / s:

root@mysql-test:~# iostat -xdm 5 /dev/sda
Linux 3.2.0-40-generic (mysql-test)  04/30/2014      _x86_64_        (4 CPU)

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.14    24.82   18.26   88.79     0.75     4.61   102.56     2.83   26.39   19.29   27.85   2.46  26.31

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00    69.29  151.52   72.73     5.31     0.59    53.95     1.21    5.39    7.84    0.29   4.39  98.51

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00   153.06  144.29  174.69     4.96     1.36    40.54     1.39    4.36    8.91    0.60   3.15 100.49

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00   105.75  150.92  109.03     4.53     0.85    42.41     1.29    4.96    8.15    0.54   3.90 101.36

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00    48.89  156.36   51.72     5.28     0.76    59.38     1.28    6.16    8.02    0.55   4.77  99.23

Während die 150 IOPS einfach das sind, was eine einzelne Festplatte in der angegebenen Konfiguration in Bezug auf zufällige E / A liefern kann, überrascht mich das Ergebnis immer noch sehr , da ich erwarten würde, dass MySQL asynchrone E / A für Lese- und Abrufvorgänge ausführen kann Eine große Anzahl von Blöcken gleichzeitig, anstatt sie einzeln zu lesen und auszuwerten, wodurch die in RAID-Konfigurationen verfügbaren Parallelisierungsgewinne effektiv vernachlässigt werden. Welche Entwurfsentscheidung oder Konfigurationsoption ist dafür verantwortlich? Ist das ein plattformspezifisches Problem?

Während ich dies mit großen MyISAM-Tabellen getestet habe, sehe ich ähnliche Effekte mit denselben Tabellen, die in InnoDB konvertiert wurden (obwohl nicht so schlecht, dauert die Beispielabfrage immer noch 20 bis 30 Sekunden, wobei die meiste Zeit für das Lesen der Festplatte aufgewendet wird eine Warteschlangenlänge von 1) nach dem Neustart des MySQL-Daemons und daher sind die Pufferpools leer. Ich habe auch überprüft, dass das gleiche Problem bei 5.6 GA und dem aktuellen 5.7-Meilenstein 14 weiterhin besteht. Solange ich einen einzelnen Abfragethread verwende, scheint MySQL nicht in der Lage zu sein, die für die Abfrageverarbeitung erforderlichen E / A-Vorgänge zu parallelisieren.


Auf Anfrage einige zusätzliche Details zum Szenario. Das Verhalten kann mit einer Vielzahl von Abfragetypen beobachtet werden. Ich habe willkürlich einen für weitere Tests ausgewählt, der ungefähr so ​​lautet:

SELECT herp.id, herp.firstname, herp.lastname, derp.label, herp.email, 
(SELECT CONCAT(label, " (", zip_code, " ", city,")" ) FROM subsidiaries WHERE subsidiaries.id=herp.subsidiary_id ) AS subsidiary, 
(SELECT COUNT(fk_herp) from herp_missing_data WHERE fk_herp=herp.id) AS missing_data
FROM herp LEFT JOIN derp ON derp.id=herp.fk_derp
WHERE (herp.fk_pools='123456')  AND herp.city LIKE '%Some City%' AND herp.active='yes' 
ORDER BY herp.id desc LIMIT 0,10;

Ich weiß, dass es Raum für Optimierungen gibt, aber ich habe mich aus mehreren Gründen entschlossen, dies zu belassen und mich darauf zu konzentrieren, eine allgemeine Erklärung für das unerwartete E / A-Muster zu finden, das ich sehe.

Die verwendeten Tabellen enthalten eine Reihe von Daten:

mysql> select table_name, engine, table_rows, data_length, index_length from information_schema.tables WHERE tables.TABLE_SCHEMA = 'mydb' and tables.table_name in ( 'herp', 'derp', 'missing_data', 'subsidiaries');
+-------------------------+--------+------------+-------------+--------------+
| table_name              | engine | table_rows | data_length | index_length |
+-------------------------+--------+------------+-------------+--------------+
| derp                    | MyISAM |      14085 |     1118676 |       165888 |
| herp                    | MyISAM |     821747 |   828106512 |    568057856 |
| missing_data            | MyISAM |    1220186 |    15862418 |     29238272 |
| subsidiaries            | MyISAM |       1499 |     6490308 |       103424 |
+-------------------------+--------+------------+-------------+--------------+
4 rows in set (0.00 sec)

Wenn ich jetzt die obige Abfrage über diese Tabellen ausführe, erhalte ich Ausführungszeiten von über 1 Minute, während das System anscheinend ständig damit beschäftigt ist, Daten von der Festplatte mit einem einzelnen Thread zu lesen.

Das Profil für eine Beispielabfrageausführung (die in diesem Beispiel 1 Minute und 9,17 Sekunden dauerte) sieht folgendermaßen aus:

mysql> show profile for query 1;
+--------------------------------+-----------+
| Status                         | Duration  |
+--------------------------------+-----------+
| starting                       |  0.000118 |
| Waiting for query cache lock   |  0.000035 |
| init                           |  0.000033 |
| checking query cache for query |  0.000399 |
| checking permissions           |  0.000077 |
| checking permissions           |  0.000030 |
| checking permissions           |  0.000031 |
| checking permissions           |  0.000035 |
| Opening tables                 |  0.000158 |
| init                           |  0.000294 |
| System lock                    |  0.000056 |
| Waiting for query cache lock   |  0.000032 |
| System lock                    |  0.000116 |
| optimizing                     |  0.000063 |
| statistics                     |  0.001964 |
| preparing                      |  0.000104 |
| Sorting result                 |  0.000033 |
| executing                      |  0.000030 |
| Sending data                   |  2.031349 |
| optimizing                     |  0.000054 |
| statistics                     |  0.000039 |
| preparing                      |  0.000024 |
| executing                      |  0.000013 |
| Sending data                   |  0.000044 |
| optimizing                     |  0.000017 |
| statistics                     |  0.000021 |
| preparing                      |  0.000019 |
| executing                      |  0.000013 |
| Sending data                   | 21.477528 |
| executing                      |  0.000070 |
| Sending data                   |  0.000075 |
| executing                      |  0.000027 |
| Sending data                   | 45.692623 |
| end                            |  0.000076 |
| query end                      |  0.000036 |
| closing tables                 |  0.000109 |
| freeing items                  |  0.000067 |
| Waiting for query cache lock   |  0.000038 |
| freeing items                  |  0.000080 |
| Waiting for query cache lock   |  0.000044 |
| freeing items                  |  0.000037 |
| storing result in query cache  |  0.000033 |
| logging slow query             |  0.000103 |
| cleaning up                    |  0.000073 |
+--------------------------------+-----------+
44 rows in set, 1 warning (0.00 sec)
syneticon-dj
quelle
Haben Sie einen wiederholbaren (idealerweise einfachen) Testfall, den Sie genauer erklären könnten? ZB eine Abfrage, die dieses Verhalten erzeugt? Unter welchen Umständen? Sie haben diesen Weg mit "echo 3> ..." und "mysql daemon neu starten" begonnen, sind aber nicht ins Detail gegangen.
Scott Leadley
@ ScottLeadley, danke, dass du dir das angeschaut hast. Ich glaube nicht, dass ich es "einfach" machen könnte - das Problem wäre nur zu beobachten, wenn eine große Datenmenge für eine einzelne Abfrage gelesen werden müsste und es sich meistens um zufällige E / A handelt. Tabellen und Abfragen sind relativ einfach, und obwohl ich die DDL- und Abfragetexte veröffentlichen könnte, bezweifle ich, dass jemand sie sofort reproduzieren kann, wenn die Tabellen- / Indexdaten nicht auf Hunderte von Megabyte angewachsen sind.
Syneticon-DJ
Wie Sie bereits angedeutet haben, entspricht die Wartezeit von 5 ms für Lesevorgänge der durchschnittlichen Rotationslatenz einer Festplatte mit 5400 U / min. Suchen Sie nach Konflikten, wenn Sie "eine große Datenmenge ... meistens zufällige E / A" lesen. Sie haben RAID erwähnt, aber keine Details zu dieser speziellen Konfiguration angegeben.
Scott Leadley
Ich bin mir nicht sicher, ob ich Ihnen direkt helfen kann, da ich Ihre Konfiguration nicht ausführe. Die Faustregel von StackExchange lautet jedoch, dass eine wirklich gute Frage mehr Aufmerksamkeit erhält als ein Kopfgeld. Schreiben der perfekten Frage
Scott Leadley
@ScottLeadley die 5 ms warten sind hauptsächlich auf die Latenz des verwendeten Speichersystems zurückzuführen. Ich habe dies in verschiedenen Szenarien getestet - von einem einfachen 4-Festplatten-RAID10 bis zu einem Tiered Storage Filer mit einem 16-Festplatten-Shelf und SSD-Backing. Die Ergebnisse zeigen durchweg, dass die E / A-Last nicht parallelisiert und somit latenzgebunden ist. Was ich für grundlegend falsch halte. Ich habe die Abfragedetails zu der Frage hinzugefügt, bin jedoch noch nicht davon überzeugt, dass sie eine große Hilfe darstellen würden.
Syneticon-DJ

Antworten:

8

Lassen Sie mich zunächst klarstellen, dass MyISAM keine asynchronen E / A-Vorgänge ausführt, InnoDB dies jedoch standardmäßig in MySQL 5.5 tut und tun wird. Vor 5.5 wurde "simuliertes AIO" unter Verwendung von Arbeitsthreads verwendet.

Ich denke, es ist auch wichtig, zwischen drei Situationen zu unterscheiden:

  1. Mehrere Abfragen werden gleichzeitig ausgeführt
  2. Eine einzelne Abfrage, die parallel ausgeführt wird
  3. Eine Art logisches Vorauslesen für Tabellenscans / Clear-Fälle, in denen die nächsten Seiten bekannt sind.

Für (1) E / A kann hierfür parallel ausgeführt werden. Bei MyISAM gibt es einige Einschränkungen: Tabellensperre und eine globale Sperre, die den key_buffer(Index-Cache) schützt . InnoDB in MySQL 5.5+ glänzt hier wirklich.

Für (2) wird dies derzeit nicht unterstützt. Ein guter Anwendungsfall wäre die Partitionierung, bei der Sie jede partitionierte Tabelle parallel durchsuchen können.

Für (3) InnoDB verfügt über eine lineare Vorauslesung, um einen vollständigen Umfang (Gruppe von 64 Seiten) zu lesen, wenn> 56 Seiten gelesen werden (dies ist konfigurierbar), aber es gibt Raum für weitere Verbesserungen. Facebook hat über die Implementierung von Logical-Readhead in seinem Zweig geschrieben (mit einem 10-fachen Leistungsgewinn bei Tischscans).

Morgan Tocker
quelle
Vielen Dank, dies gibt mir ein zusätzliches Verständnis für das, was ich sehe. Bedeutet dies im Allgemeinen, dass MyISAM nicht mehr als eine Festplatte mit IOPS für eine Single-Threaded-Last verwenden kann? Ich kann in den Dokumenten keine Hinweise darauf finden - haben Sie zufällig etwas zur Hand?
Syneticon-DJ
Ja. Ich kann mir keinen Ort in den Dokumenten vorstellen, an dem dies sein würde.
Morgan Tocker
2

Ich hoffe, es missing_dataist nicht MyISAM, da eine leere MyISAM-Tabelle normalerweise 1024 Byte hat .MYI. Von einem MyISAM wird eine Bytegröße ungleich Null erwartet. Ein Null-Byte .MYIklingt für mich etwas gruselig.

Wenn Sie diese Metadatenabfrage ausführen

select table_name, table_rows, data_length, index_length, engine
from information_schema.tables
WHERE tables.TABLE_SCHEMA = 'mydb'
and tables.table_name = 'missing_data';

und die Engine dieser Tabelle ist MyISAM. Sie müssen sie reparieren.

SEITLICHER HINWEIS: Wenn dies der Fall engineist NULL, handelt es sich um eine Ansicht. Wenn es sich um eine Ansicht handelt oder nicht um MyISAM, ignorieren Sie bitte den Rest meines Beitrags und fügen Sie diese Informationen der Frage hinzu. Wenn die Tabelle MyISAM ist, lesen Sie weiter ...

Laut Ihrer Metadatenabfrage sind missing_data.MYDes ungefähr 46 Millionen.

Führen Sie dies zuerst aus

SHOW CREATE TABLE mydb.missing_data\G

Sie erhalten entweder die Tabellenbeschreibung oder eine Fehlermeldung mit der Aufschrift "Ähnliches"

ERROR 126 (HY000): Incorrect key file for table ...

Wenn Sie die Tabellenbeschreibung erhalten und es sich um MyISAM handelt, führen Sie es bitte aus

OPTIMIZE TABLE mydb.missing_data;

Die Tabelle wird ohne Fragmentierung neu erstellt und neue Indexstatistiken berechnet. Wenn das nicht funktioniert, versuchen Sie:

REPAIR TABLE mydb.missing_data;

Dadurch sollten die Indexseiten für MyISAM neu generiert werden.

Führen Sie dies nach der Reparatur aus Sicherheitsgründen aus (wenn Sie MySQL 5.6 verwenden)

FLUSH TABLES mydb.missing_data;

Ihre Frage

Die Indizes Ihrer Tabelle werden möglicherweise nicht in den Speicher geladen, wenn das MySQL Query Optimizer keine Verwendung verwendet. Wenn Ihre WHERE-Klausel vorschreibt, dass eine erhebliche Anzahl von Zeilen aus den Indizes gelesen werden muss, erkennt MySQL Query Optimizer dies beim Erstellen des EXPLAIN-Plans und entscheidet sich stattdessen für einen vollständigen Tabellenscan.

Parallele E / A-Operationen für eine MyISAM-Tabelle sind nicht erreichbar, da sie nicht konfigurierbar sind.

InnoDB kann so eingestellt werden, dass die Leistung gesteigert wird.

RolandoMySQLDBA
quelle
Ich muss es noch einmal betonen: Wenn mydb.missing_data MyISAM ist und einen Null-Byte-Index hat, stimmt definitiv etwas nicht.
RolandoMySQLDBA
Ich habe die Daten so aktualisiert, dass sie kohärenter sind. Jetzt werden nur MyISAM-Ergebnisse von einem einzelnen Host angezeigt, damit die Leute nicht verwirrt werden.
Syneticon-DJ