Langsame Abfragen, ineffiziente Abfragen oder lang andauernde Abfragen sind Probleme, die DBAs regelmäßig plagen. Sie sind allgegenwärtig und dennoch ein unvermeidlicher Teil des Lebens für jeden, der für die Verwaltung einer Datenbank verantwortlich ist.
Ein schlechtes Datenbankdesign kann die Effizienz der Abfrage und ihre Leistung beeinträchtigen. Mangelndes Wissen oder die unsachgemäße Verwendung von Funktionsaufrufen, gespeicherten Prozeduren oder Routinen können ebenfalls zu einer Verschlechterung der Datenbankleistung führen und sogar dem gesamten MySQL-Datenbankcluster schaden.
Bei einer Master-Slave-Replikation sind Tabellen mit fehlenden Primär- oder Sekundärindizes eine sehr häufige Ursache für diese Probleme. Dies verursacht Slave-Lags, die sehr lange anhalten können (im schlimmsten Fall).
In dieser zweiteiligen Blogserie geben wir Ihnen einen Auffrischungskurs darüber, wie Sie die Maximierung Ihrer Datenbankabfragen in MySQL angehen können, um eine bessere Effizienz und Leistung zu erzielen.
Fügen Sie Ihrer Tabelle immer einen eindeutigen Index hinzu
Tabellen, die keine Primär- oder eindeutigen Schlüssel haben, verursachen normalerweise große Probleme, wenn die Daten größer werden. In diesem Fall kann eine einfache Datenänderung die Datenbank zum Stillstand bringen. Wenn keine geeigneten Indizes vorhanden sind und eine UPDATE- oder DELETE-Anweisung auf die bestimmte Tabelle angewendet wurde, wird ein vollständiger Tabellenscan als Abfrageplan von MySQL ausgewählt. Dies kann zu hohen Festplatten-E/A-Vorgängen für Lese- und Schreibvorgänge führen und die Leistung Ihrer Datenbank beeinträchtigen. Siehe ein Beispiel unten:
root[test]> show create table sbtest2\G
*************************** 1. row ***************************
Table: sbtest2
Create Table: CREATE TABLE `sbtest2` (
`id` int(10) unsigned NOT NULL,
`k` int(10) unsigned NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
root[test]> explain extended update sbtest2 set k=52, pad="xx234xh1jdkHdj234" where id=57;
+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| 1 | UPDATE | sbtest2 | NULL | ALL | NULL | NULL | NULL | NULL | 1923216 | 100.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.06 sec)
Während eine Tabelle mit Primärschlüssel einen sehr guten Abfrageplan hat,
root[test]> show create table sbtest3\G
*************************** 1. row ***************************
Table: sbtest3
Create Table: CREATE TABLE `sbtest3` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`k` int(10) unsigned NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=2097121 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
root[test]> explain extended update sbtest3 set k=52, pad="xx234xh1jdkHdj234" where id=57;
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| 1 | UPDATE | sbtest3 | NULL | range | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Using where |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
Primäre oder eindeutige Schlüssel stellen eine wichtige Komponente für eine Tabellenstruktur dar, da dies sehr wichtig ist, insbesondere wenn eine Tabelle gewartet wird. Beispielsweise empfiehlt die Verwendung von Tools aus dem Percona-Toolkit (z. B. pt-online-schema-change oder pt-table-sync), dass Sie über eindeutige Schlüssel verfügen müssen. Denken Sie daran, dass der PRIMARY KEY bereits ein eindeutiger Schlüssel ist und ein Primärschlüssel keine NULL-Werte enthalten kann, sondern einen eindeutigen Schlüssel. Das Zuweisen eines NULL-Werts zu einem Primärschlüssel kann einen Fehler wie
verursachenERROR 1171 (42000): All parts of a PRIMARY KEY must be NOT NULL; if you need NULL in a key, use UNIQUE instead
Für Slave-Knoten ist es auch üblich, dass in bestimmten Fällen der primäre/eindeutige Schlüssel nicht in der Tabelle vorhanden ist, was daher eine Diskrepanz der Tabellenstruktur darstellt. Sie können mysqldiff verwenden, um dies zu erreichen, oder Sie können mysqldump --no-data … params und einen Diff ausführen, um die Tabellenstruktur zu vergleichen und zu prüfen, ob es Abweichungen gibt.
Tabellen mit doppelten Indizes scannen und dann löschen
Duplizierte Indizes können auch zu Leistungseinbußen führen, insbesondere wenn die Tabelle eine große Anzahl von Datensätzen enthält. MySQL muss mehrere Versuche durchführen, um die Abfrage zu optimieren, und führt mehr Abfragepläne zur Überprüfung durch. Es umfasst das Scannen großer Indexverteilungen oder Statistiken und erhöht die Leistung, da es zu Speicherkonflikten oder einer hohen E/A-Speicherauslastung kommen kann.
Die Verschlechterung für Abfragen, wenn doppelte Indizes in einer Tabelle beobachtet werden, ist auch ein Attribut für die Sättigung des Pufferpools. Dies kann auch die Leistung von MySQL beeinträchtigen, wenn das Checkpointing die Transaktionsprotokolle auf die Festplatte schreibt. Dies liegt an der Verarbeitung und Speicherung eines unerwünschten Indexes (der in Wirklichkeit Platzverschwendung im jeweiligen Tablespace dieser Tabelle ist). Beachten Sie, dass auch doppelte Indizes im Tablespace gespeichert werden, der ebenfalls im Pufferpool gespeichert werden muss.
Schauen Sie sich die folgende Tabelle an, die mehrere doppelte Schlüssel enthält:
root[test]#> show create table sbtest3\G
*************************** 1. row ***************************
Table: sbtest3
Create Table: CREATE TABLE `sbtest3` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`k` int(10) unsigned NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k` (`k`,`pad`,`c`),
KEY `kcp2` (`id`,`k`,`c`,`pad`),
KEY `kcp` (`k`,`c`,`pad`),
KEY `pck` (`pad`,`c`,`id`,`k`)
) ENGINE=InnoDB AUTO_INCREMENT=2048561 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
und hat eine Größe von 2,3 GiB
root[test]#> \! du -hs /var/lib/mysql/test/sbtest3.ibd
2.3G /var/lib/mysql/test/sbtest3.ibd
Lassen Sie uns die doppelten Indizes löschen und die Tabelle mit einem No-Op-Alter neu aufbauen,
root[test]#> drop index kcp2 on sbtest3; drop index kcp on sbtest3 drop index pck on sbtest3;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
root[test]#> alter table sbtest3 engine=innodb;
Query OK, 0 rows affected (28.23 sec)
Records: 0 Duplicates: 0 Warnings: 0
root[test]#> \! du -hs /var/lib/mysql/test/sbtest3.ibd
945M /var/lib/mysql/test/sbtest3.ibd
Es war in der Lage, bis zu ~59% der alten Größe des Tabellenbereichs einzusparen, was wirklich riesig ist.
Um doppelte Indizes zu ermitteln, können Sie pt-duplicate-checker verwenden, um den Job für Sie zu erledigen.
Tue deinen Pufferpool auf
In diesem Abschnitt beziehe ich mich nur auf die InnoDB-Speicher-Engine.
Pufferpool ist eine wichtige Komponente innerhalb des InnoDB-Kernelraums. Hier speichert InnoDB beim Zugriff Tabellen- und Indexdaten. Es beschleunigt die Verarbeitung, da häufig verwendete Daten mithilfe von BTREE effizient im Speicher abgelegt werden. Wenn Sie beispielsweise mehrere Tabellen haben, die aus>=100 GiB bestehen und auf die häufig zugegriffen wird, empfehlen wir Ihnen, einen schnellen flüchtigen Speicher ab einer Größe von 128 GiB zu delegieren und den Pufferpool mit 80 % des physischen Speichers zuzuweisen. Die 80 % müssen effizient überwacht werden. Sie können SHOW ENGINE INNODB STATUS \G verwenden oder Überwachungssoftware wie ClusterControl nutzen, die eine feinkörnige Überwachung bietet, die den Pufferpool und seine relevanten Integritätsmetriken umfasst. Setzen Sie auch die Variable innodb_buffer_pool_instances entsprechend. Sie können dies auf größer als 8 festlegen (Standard, wenn innodb_buffer_pool_size>=1GiB), z. B. 16, 24, 32 oder 64 oder bei Bedarf höher.
Beim Überwachen des Pufferpools müssen Sie die globale Statusvariable Innodb_buffer_pool_pages_free überprüfen, die Ihnen Hinweise gibt, ob der Pufferpool angepasst werden muss, oder vielleicht überlegen, ob es auch unerwünschte oder doppelte Indizes gibt, die den verbrauchen Puffer. Der SHOW ENGINE INNODB STATUS \G bietet auch einen detaillierteren Aspekt der Pufferpoolinformationen, einschließlich seines individuellen Pufferpools basierend auf der Anzahl der von Ihnen festgelegten innodb_buffer_pool_instances.
Verwenden Sie VOLLTEXT-Indizes (aber nur, wenn zutreffend)
Mit Abfragen wie,
SELECT bookid, page, context FROM books WHERE context like '%for dummies%';
wobei Kontext eine Spalte vom Typ String (char, varchar, text) ist, ist ein Beispiel für eine super schlechte Abfrage! Das Abrufen großer Datensätze mit einem Filter, der gierig sein muss, endet mit einem vollständigen Tabellenscan, und das ist einfach verrückt. Erwägen Sie die Verwendung des FULLTEXT-Index. A FULLTEXT-Indizes haben ein invertiertes Indexdesign. Invertierte Indizes speichern eine Liste von Wörtern und für jedes Wort eine Liste von Dokumenten, in denen das Wort vorkommt. Zur Unterstützung der Näherungssuche werden auch Positionsinformationen für jedes Wort als Byte-Offset gespeichert.
Um FULLTEXT zum Suchen oder Filtern von Daten zu verwenden, müssen Sie die Kombination aus MATCH() ...AGAINST-Syntax verwenden und nicht wie die obige Abfrage. Natürlich müssen Sie das Feld als Ihr FULLTEXT-Indexfeld angeben.
Um einen VOLLTEXT-Index zu erstellen, geben Sie einfach VOLLTEXT als Ihren Index an. Siehe das Beispiel unten:
root[minime]#> CREATE FULLTEXT INDEX aboutme_fts ON users_info(aboutme);
Query OK, 0 rows affected, 1 warning (0.49 sec)
Records: 0 Duplicates: 0 Warnings: 1
root[jbmrcd_date]#> show warnings;
+---------+------+--------------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------------+
| Warning | 124 | InnoDB rebuilding table to add column FTS_DOC_ID |
+---------+------+--------------------------------------------------+
1 row in set (0.00 sec)
Obwohl die Verwendung von FULLTEXT-Indizes bei der Suche nach Wörtern in einem sehr großen Kontext innerhalb einer Spalte Vorteile bieten kann, führt sie bei falscher Verwendung auch zu Problemen.
Bei einer FULLTEXT-Suche nach einer großen Tabelle, auf die ständig zugegriffen wird (bei der mehrere Clientanfragen nach unterschiedlichen, eindeutigen Schlüsselwörtern suchen), kann dies sehr CPU-intensiv sein.
Es gibt auch Fälle, in denen FULLTEXT nicht anwendbar ist. Siehe diesen externen Blogbeitrag. Obwohl ich dies nicht mit 8.0 versucht habe, sehe ich keine diesbezüglich relevanten Änderungen. Wir empfehlen, FULLTEXT nicht für die Suche in einer Big-Data-Umgebung zu verwenden, insbesondere nicht für Tabellen mit hohem Datenverkehr. Versuchen Sie andernfalls, andere Technologien wie Apache Lucene, Apache Solr, tsearch2 oder Sphinx zu nutzen.
Vermeiden Sie die Verwendung von NULL in Spalten
Spalten, die Nullwerte enthalten, sind in MySQL völlig in Ordnung. Wenn Sie jedoch Spalten mit Nullwerten in einem Index verwenden, kann dies die Abfrageleistung beeinträchtigen, da der Optimierer aufgrund einer schlechten Indexverteilung nicht den richtigen Abfrageplan bereitstellen kann. Es gibt jedoch bestimmte Möglichkeiten, Abfragen zu optimieren, die Nullwerte enthalten, aber natürlich, wenn dies den Anforderungen entspricht. Bitte überprüfen Sie die Dokumentation von MySQL zur Null-Optimierung. Sie können auch diesen externen Beitrag lesen, der ebenfalls hilfreich ist.
Entwerfen Sie Ihre Schematopologie und Tabellenstruktur effizient
Bis zu einem gewissen Grad bietet Ihnen die Normalisierung Ihrer Datenbanktabellen von 1NF (erste Normalform) auf 3NF (dritte Normalform) einige Vorteile für die Abfrageeffizienz, da normalisierte Tabellen dazu neigen, redundante Datensätze zu vermeiden. Eine ordnungsgemäße Planung und Gestaltung Ihrer Tabellen ist sehr wichtig, da Sie auf diese Weise Daten abrufen oder abrufen und jede dieser Aktionen mit Kosten verbunden ist. Bei normalisierten Tabellen besteht das Ziel der Datenbank darin sicherzustellen, dass jede Nichtschlüsselspalte in jeder Tabelle direkt vom Schlüssel abhängig ist; der ganze Schlüssel und nichts als der Schlüssel. Wenn dieses Ziel erreicht wird, zahlen sich die Vorteile in Form von weniger Redundanzen, weniger Anomalien und verbesserter Effizienz aus.
Obwohl das Normalisieren Ihrer Tabellen viele Vorteile hat, bedeutet das nicht, dass Sie alle Ihre Tabellen auf diese Weise normalisieren müssen. Mit Star Schema können Sie ein Design für Ihre Datenbank implementieren. Das Entwerfen Ihrer Tabellen mit Sternschema hat den Vorteil einfacherer Abfragen (Vermeidung komplexer Kreuzverknüpfungen), eines einfachen Abrufs von Daten für die Berichterstellung und bietet Leistungssteigerungen, da keine Vereinigungen oder komplexen Verknüpfungen oder schnelle Aggregationen verwendet werden müssen. Ein Sternschema ist einfach zu implementieren, aber Sie müssen sorgfältig planen, da es große Probleme und Nachteile verursachen kann, wenn Ihre Tabelle größer wird und gewartet werden muss. Das Star-Schema (und die zugrunde liegenden Tabellen) sind anfällig für Datenintegritätsprobleme, sodass Sie eine hohe Wahrscheinlichkeit haben, dass ein Haufen Ihrer Daten redundant ist. Wenn Sie der Meinung sind, dass diese Tabelle konstant sein muss (Struktur und Design) und darauf ausgelegt ist, die Abfrageeffizienz zu nutzen, dann ist dies ein idealer Fall für diesen Ansatz.
Das Mischen Ihrer Datenbankdesigns (solange Sie bestimmen und identifizieren können, welche Art von Daten in Ihre Tabellen gezogen werden müssen) ist sehr wichtig, da Sie von effizienteren Abfragen profitieren können dem DBA bei Backups, Wartung und Wiederherstellung helfen.
Werden Sie konstante und alte Daten los
Wir haben kürzlich einige Best Practices für die Archivierung Ihrer Datenbank in der Cloud geschrieben. Es behandelt, wie Sie die Vorteile der Datenarchivierung nutzen können, bevor sie in die Cloud gehen. Wie trägt also das Entfernen alter Daten oder das Archivieren Ihrer konstanten und alten Daten zur Effizienz der Abfrage bei? Wie in meinem vorherigen Blog erwähnt, gibt es Vorteile für größere Tabellen, die ständig geändert und mit neuen Daten eingefügt werden, der Tablespace kann schnell wachsen. MySQL und InnoDB arbeiten effizient, wenn Datensätze oder Daten zusammenhängend sind und eine Bedeutung für die nächste Zeile in der Tabelle haben. Das heißt, wenn Sie keine alten Datensätze haben, die nicht mehr verwendet werden müssen, muss der Optimierer diese nicht in die Statistik aufnehmen, was ein viel effizienteres Ergebnis bietet. Sinnvoll, oder? Außerdem liegt die Abfrageeffizienz nicht nur auf der Anwendungsseite, sondern muss auch ihre Effizienz bei der Durchführung eines Backups und bei Wartung oder Failover berücksichtigen. Wenn Sie beispielsweise eine schlechte und lange Abfrage haben, die sich auf Ihren Wartungszeitraum oder ein Failover auswirken kann, kann dies ein Problem darstellen.
Aktivieren Sie die Abfrageprotokollierung nach Bedarf
Stellen Sie das Slow-Query-Log Ihres MySQL immer entsprechend Ihren individuellen Anforderungen ein. Wenn Sie Percona Server verwenden, können Sie die erweiterte langsame Abfrageprotokollierung nutzen. Es erlaubt Ihnen, bestimmte Variablen üblich zu definieren. Sie können kombinierte Abfragetypen wie full_scan, full_join, tmp_table usw. filtern. Sie können auch die Rate der langsamen Abfrageprotokollierung durch die Variable log_slow_rate_type und viele andere diktieren.
Die Bedeutung der Aktivierung der Abfrageprotokollierung in MySQL (z. B. langsame Abfrage) ist vorteilhaft für die Überprüfung Ihrer Abfragen, sodass Sie Ihr MySQL optimieren oder optimieren können, indem Sie bestimmte Variablen an Ihre Anforderungen anpassen. Stellen Sie zum Aktivieren des langsamen Abfrageprotokolls sicher, dass diese Variablen eingerichtet sind:
- long_query_time - Weisen Sie den richtigen Wert zu, wie lange die Abfragen dauern dürfen. Wenn die Abfragen länger als 10 Sekunden dauern (Standard), wird dies auf die von Ihnen zugewiesene Protokolldatei für langsame Abfragen reduziert.
- slow_query_log - um es zu aktivieren, setzen Sie es auf 1.
- slow_query_log_file - Dies ist der Zielpfad für Ihre Protokolldatei für langsame Abfragen.
Das Protokoll für langsame Abfragen ist sehr hilfreich für die Abfrageanalyse und die Diagnose fehlerhafter Abfragen, die zu Verzögerungen, Slave-Verzögerungen, lang andauernden Abfragen, Speicher- oder CPU-intensiv oder sogar zum Absturz des Servers führen. Wenn Sie pt-query-digest oder pt-index-usage verwenden, verwenden Sie die Protokolldatei für langsame Abfragen als Quellziel, um diese Abfragen gleichermaßen zu melden.
Fazit
Wir haben in diesem Blog einige Methoden besprochen, mit denen Sie die Effizienz von Datenbankabfragen maximieren können. In diesem nächsten Teil werden wir noch mehr Faktoren besprechen, die Ihnen helfen können, die Leistung zu maximieren. Bleiben Sie dran!