Sie können eine MySQL-Datenbank auf mehrere Arten herunterfahren. Einige offensichtliche Möglichkeiten sind das Herunterfahren des Hosts, das Herausziehen des Stromkabels oder das harte Beenden des mysqld-Prozesses mit SIGKILL, um ein unsauberes Abschaltverhalten von MySQL zu simulieren. Aber es gibt auch weniger subtile Möglichkeiten, Ihren MySQL-Server absichtlich zum Absturz zu bringen und dann zu sehen, welche Art von Kettenreaktion er auslöst. Warum würden Sie das tun wollen? Fehler und Wiederherstellung können viele Sonderfälle haben, und das Verständnis dieser Fälle kann dazu beitragen, das Überraschungsmoment zu reduzieren, wenn Dinge in der Produktion passieren. Idealerweise möchten Sie Ausfälle in einer kontrollierten Umgebung simulieren und dann Datenbank-Failover-Verfahren entwerfen und testen.
Es gibt mehrere Bereiche in MySQL, die wir angehen können, je nachdem, wie es fehlschlagen oder abstürzen soll. Sie können den Tablespace beschädigen, die MySQL-Puffer und -Caches überlaufen lassen, die Ressourcen begrenzen, um den Server auszuhungern, und auch mit Berechtigungen herumspielen. In diesem Blogbeitrag zeigen wir Ihnen einige Beispiele, wie Sie einen MySQL-Server in einer Linux-Umgebung zum Absturz bringen können. Einige von ihnen wären geeignet für z.B. Amazon RDS-Instances, bei denen Sie keinen Zugriff auf den zugrunde liegenden Host hätten.
Töten, töten, töten, sterben, sterben, sterben
Der einfachste Weg, einen MySQL-Server zum Scheitern zu bringen, besteht darin, den Prozess oder Host einfach zu beenden und MySQL keine Chance zu geben, ein ordnungsgemäßes Herunterfahren durchzuführen. Um einen mysqld-Absturz zu simulieren, senden Sie einfach das Signal 4, 6, 7, 8 oder 11 an den Prozess:
$ kill -11 $(pidof mysqld)
Wenn Sie sich das MySQL-Fehlerprotokoll ansehen, können Sie die folgenden Zeilen sehen:
11:06:09 UTC - mysqld got signal 11 ;
This could be because you hit a bug. It is also possible that this binary
or one of the libraries it was linked against is corrupt, improperly built,
or misconfigured. This error can also be caused by malfunctioning hardware.
Attempting to collect some information that could help diagnose the problem.
As this is a crash and something is definitely wrong, the information
collection process might fail.
..
Attempting backtrace. You can use the following information to find out
where mysqld died. If you see no messages after this, something went
terribly wrong...
Sie können auch kill -9 (SIGKILL) verwenden, um den Prozess sofort zu beenden. Weitere Details zum Linux-Signal finden Sie hier. Alternativ können Sie auf der Hardwareseite einen gemeineren Weg wählen, wie z. B. das Netzkabel abziehen, den Hard-Reset-Knopf drücken oder ein Fechtgerät für STONITH verwenden.
OOM auslösen
Beliebte MySQL-in-the-Cloud-Angebote wie Amazon RDS und Google Cloud SQL haben keine einfache Möglichkeit, sie zum Absturz zu bringen. Erstens, weil Sie keinen Zugriff auf Betriebssystemebene auf die Datenbankinstanz erhalten, und zweitens, weil der Anbieter einen proprietären gepatchten MySQL-Server verwendet. Eine Möglichkeit besteht darin, einige Puffer zum Überlaufen zu bringen und den Out-of-Memory (OOM)-Manager den MySQL-Prozess rausschmeißen zu lassen.
Sie können die Größe des Sortierpuffers auf etwas erhöhen, das größer ist als das, was der Arbeitsspeicher verarbeiten kann, und eine Reihe von mysql-Sortierabfragen an den MySQL-Server senden. Lassen Sie uns eine 10-Millionen-Zeilen-Tabelle mit sysbench auf unserer Amazon RDS-Instance erstellen, damit wir eine riesige Sortierung erstellen können:
$ sysbench \
--db-driver=mysql \
--oltp-table-size=10000000 \
--oltp-tables-count=1 \
--threads=1 \
--mysql-host=dbtest.cdw9q2wnb00s.ap-tokyo-1.rds.amazonaws.com \
--mysql-port=3306 \
--mysql-user=rdsroot \
--mysql-password=password \
/usr/share/sysbench/tests/include/oltp_legacy/parallel_prepare.lua \
run
Ändern Sie die sort_buffer_size auf 5G (unsere Testinstanz ist db.t2.micro – 1 GB, 1 vCPU), indem Sie zu Amazon RDS Dashboard -> Parametergruppen -> Parametergruppe erstellen -> den Gruppennamen angeben -> Parameter bearbeiten -> „sort_buffer_size“ auswählen und angeben der Wert als 5368709120.
Wenden Sie die Änderungen der Parametergruppe an, indem Sie zu Instanzen -> Instanzaktion -> Ändern -> Datenbankoptionen -> Datenbankparametergruppe -> gehen und unsere neu erstellte Parametergruppe auswählen. Starten Sie dann die RDS-Instanz neu, um die Änderungen zu übernehmen.
Überprüfen Sie anschließend den neuen Wert von sort_buffer_size :
MySQL [(none)]> select @@sort_buffer_size;
+--------------------+
| @@sort_buffer_size |
+--------------------+
| 5368709120 |
+--------------------+
Feuern Sie dann 48 einfache Abfragen ab, die von einem Client sortiert werden müssen:
$ for i in {1..48}; do (mysql -urdsroot -ppassword -h dbtest.cdw9q2wnb00s.ap-tokyo-1.rds.amazonaws.com -e 'SELECT * FROM sbtest.sbtest1 ORDER BY c DESC >/dev/null &); done
Wenn Sie das Obige auf einem Standardhost ausführen, werden Sie feststellen, dass der MySQL-Server beendet wird, und Sie können die folgenden Zeilen im Syslog oder dmesg des Betriebssystems sehen:
[164199.868060] Out of memory: Kill process 47060 (mysqld) score 847 or sacrifice child
[164199.868109] Killed process 47060 (mysqld) total-vm:265264964kB, anon-rss:3257400kB, file-rss:0kB
Mit systemd werden MySQL oder MariaDB automatisch neu gestartet, ebenso Amazon RDS. Sie können sehen, dass die Betriebszeit für unsere RDS-Instanz auf 0 zurückgesetzt wird (unter mysqladmin-Status) und der Wert „Letzte Wiederherstellungszeit“ (unter RDS-Dashboard) auf den Moment aktualisiert wird, in dem er ausgefallen ist.
Beschädigung der Daten
InnoDB verfügt über einen eigenen System-Tablespace zum Speichern von Datenwörterbüchern, Puffern und Rollback-Segmenten in einer Datei namens ibdata1. Es speichert auch den gemeinsam genutzten Tablespace, wenn Sie innodb_file_per_table nicht konfigurieren (in MySQL 5.6.6+ standardmäßig aktiviert). Wir können diese Datei einfach auf Null setzen, eine Schreiboperation senden und Tabellen leeren, um mysqld zum Absturz zu bringen:
# empty ibdata1
$ cat /dev/null > /var/lib/mysql/ibdata1
# send a write
$ mysql -uroot -p -e 'CREATE TABLE sbtest.test (id INT)'
# flush tables
$ mysql -uroot -p -e 'FLUSH TABLES WITH READ LOCK; UNLOCK TABLES'
Nachdem Sie einen Schreibvorgang gesendet haben, werden Sie im Fehlerprotokoll Folgendes bemerken:
2017-11-15T06:01:59.345316Z 0 [ERROR] InnoDB: Tried to read 16384 bytes at offset 98304, but was only able to read 0
2017-11-15T06:01:59.345332Z 0 [ERROR] InnoDB: File (unknown): 'read' returned OS error 0. Cannot continue operation
2017-11-15T06:01:59.345343Z 0 [ERROR] InnoDB: Cannot continue operation.
An diesem Punkt bleibt mysql hängen, da es keine Operation ausführen kann, und nach dem Leeren erhalten Sie die Zeilen „mysqld got signal 11“ und mysqld wird heruntergefahren. Zum Aufräumen müssen Sie das beschädigte ibdata1 sowie ib_logfile* entfernen, da die Redo-Log-Dateien nicht mit einem neuen System-Tablespace verwendet werden können, der beim nächsten Neustart von mysqld generiert wird. Datenverlust ist zu erwarten.
Für MyISAM-Tabellen können wir mit .MYD (MyISAM-Datendatei) und .MYI (MyISAM-Index) unter dem MySQL-Datenverzeichnis herumspielen. Beispielsweise ersetzt der folgende Befehl jedes Vorkommen der Zeichenfolge „F“ durch „9“ in einer Datei:
$ replace F 9 -- /var/lib/mysql/sbtest/sbtest1.MYD
Senden Sie dann einige Schreibvorgänge (z. B. mit sysbench) an die Zieltabelle und führen Sie das Leeren durch:
mysql> FLUSH TABLE sbtest.sbtest1;
Folgendes sollte im MySQL-Fehlerprotokoll erscheinen:
2017-11-15T06:56:15.021564Z 448 [ERROR] /usr/sbin/mysqld: Incorrect key file for table './sbtest/sbtest1.MYI'; try to repair it
2017-11-15T06:56:15.021572Z 448 [ERROR] Got an error from thread_id=448, /export/home/pb2/build/sb_0-24964902-1505318733.42/rpm/BUILD/mysql-5.7.20/mysql-5.7.20/storage/myisam/mi_update.c:227
Die MyISAM-Tabelle wird als abgestürzt markiert und es ist notwendig, die REPAIR TABLE-Anweisung auszuführen, um sie wieder zugänglich zu machen.
Einschränkung der Ressourcen
Wir können auch die Ressourcengrenze des Betriebssystems auf unseren mysqld-Prozess anwenden, zum Beispiel die Anzahl der offenen Dateideskriptoren. Die Verwendung der Variablen open_file_limit (Standard ist 5000) erlaubt es mysqld, Dateideskriptoren mit dem Befehl setrlimit() zu reservieren. Sie können diese Variable relativ klein setzen (gerade genug, damit mysqld startet) und dann mehrere Abfragen an den MySQL-Server senden, bis das Limit erreicht ist.
Wenn mysqld auf einem systemd-Server ausgeführt wird, können wir es in der systemd-Unit-Datei unter /usr/lib/systemd/system/mysqld.service festlegen und den folgenden Wert auf einen niedrigeren Wert ändern (systemd-Standard ist 6000):
# Sets open_files_limit
LimitNOFILE = 30
Wenden Sie die Änderungen auf systemd an und starten Sie den MySQL-Server neu:
$ systemctl daemon-reload
$ systemctl restart mysqld
Beginnen Sie dann mit dem Senden neuer Verbindungen/Abfragen, die in verschiedenen Datenbanken und Tabellen zählen, sodass mysqld mehrere Dateien öffnen muss. Sie werden den folgenden Fehler bemerken:
2017-11-16T04:43:26.179295Z 4 [ERROR] InnoDB: Operating system error number 24 in a file operation.
2017-11-16T04:43:26.179342Z 4 [ERROR] InnoDB: Error number 24 means 'Too many open files'
2017-11-16T04:43:26.179354Z 4 [Note] InnoDB: Some operating system error numbers are described at http://dev.mysql.com/doc/refman/5.7/en/operating-system-error-codes.html
2017-11-16T04:43:26.179363Z 4 [ERROR] InnoDB: File ./sbtest/sbtest9.ibd: 'open' returned OS error 124. Cannot continue operation
2017-11-16T04:43:26.179371Z 4 [ERROR] InnoDB: Cannot continue operation.
2017-11-16T04:43:26.372605Z 0 [Note] InnoDB: FTS optimize thread exiting.
2017-11-16T04:45:06.816056Z 4 [Warning] InnoDB: 3 threads created by InnoDB had not exited at shutdown!
An diesem Punkt, wenn das Limit erreicht ist, friert MySQL ein und kann keine Operation mehr ausführen. Wenn Sie versuchen, eine Verbindung herzustellen, sehen Sie nach einer Weile Folgendes:
$ mysql -uroot -p
ERROR 2013 (HY000): Lost connection to MySQL server at 'reading initial communication packet', system error: 104
Mit Berechtigungen herumspielen
Der mysqld-Prozess wird vom „mysql“-Benutzer ausgeführt, was bedeutet, dass alle Dateien und Verzeichnisse, auf die er zugreifen muss, dem mysql-Benutzer/der mysql-Gruppe gehören. Indem wir die Berechtigung und den Besitz durcheinander bringen, können wir den MySQL-Server unbrauchbar machen:
$ chown root:root /var/lib/mysql
$ chmod 600 /var/lib/mysql
Generieren Sie einige Lasten für den Server und verbinden Sie sich dann mit dem MySQL-Server und leeren Sie alle Tabellen auf die Festplatte:
mysql> FLUSH TABLES WITH READ LOCK; UNLOCK TABLES;
In diesem Moment läuft mysqld noch, aber es ist irgendwie nutzlos. Sie können über einen MySQL-Client darauf zugreifen, aber Sie können keine Operation ausführen:
mysql> SHOW DATABASES;
ERROR 1018 (HY000): Can't read dir of '.' (errno: 13 - Permission denied)
Um das Chaos zu beseitigen, legen Sie die richtigen Berechtigungen fest:
$ chown mysql:mysql /var/lib/mysql
$ chmod 750 /var/lib/mysql
$ systemctl restart mysqld
Sperren
FLUSH TABLE WITH READ LOCK (FTWRL) kann unter einer Reihe von Bedingungen destruktiv sein. Wie zum Beispiel in einem Galera-Cluster, in dem alle Knoten Schreibvorgänge verarbeiten können, können Sie diese Anweisung verwenden, um den Cluster von einem der Knoten aus zu sperren. Diese Anweisung stoppt einfach andere von mysqld zu verarbeitende Abfragen während des Leerens, bis die Sperre freigegeben wird, was sehr praktisch für Sicherungsprozesse (MyISAM-Tabellen) und Dateisystem-Snapshots ist.
Obwohl diese Aktion Ihren Datenbankserver während der Sperrung nicht zum Absturz bringt oder herunterfährt, können die Folgen enorm sein, wenn die Sitzung, die die Sperre hält, sie nicht freigibt. Um dies zu versuchen, einfach:
mysql> FLUSH TABLES WITH READ LOCK;
mysql> exit
Senden Sie dann eine Reihe neuer Abfragen an mysqld, bis es max_connections erreicht Wert. Offensichtlich können Sie nicht dieselbe Sitzung wie die vorherige wiederherstellen, wenn Sie nicht mehr da sind. Die Sperre läuft also unendlich und die einzige Möglichkeit, die Sperre aufzuheben, besteht darin, die Abfrage durch einen anderen SUPER-Berechtigungsbenutzer (der eine andere Sitzung verwendet) zu beenden. Oder beenden Sie den mysqld-Prozess selbst oder führen Sie einen harten Neustart durch.
Haftungsausschluss
Dieser Blog wurde geschrieben, um Systemadministratoren und DBAs Alternativen zu bieten, um Fehlerszenarien mit MySQL zu simulieren. Probieren Sie diese nicht auf Ihrem Produktionsserver aus :-)