Du kan fjerne en MySQL-database på flere måder. Nogle oplagte måder er at lukke værten ned, trække strømkablet ud eller hårdt dræbe mysqld-processen med SIGKILL for at simulere en uren MySQL-nedlukningsadfærd. Men der er også mindre subtile måder at bevidst nedbryde din MySQL-server og derefter se, hvilken slags kædereaktion det udløser. Hvorfor vil du gøre dette? Fejl og genopretning kan have mange hjørnesager, og forståelsen af dem kan hjælpe med at reducere overraskelseselementet, når der sker ting i produktionen. Ideelt set vil du gerne simulere fejl i et kontrolleret miljø og derefter designe og teste database-failover-procedurer.
Der er flere områder i MySQL, som vi kan tackle, afhængigt af hvordan du vil have det til at fejle eller gå ned. Du kan ødelægge tablespacet, overløbe MySQL-buffere og caches, begrænse ressourcerne til at sulte serveren og også rode rundt med tilladelser. I dette blogindlæg vil vi vise dig nogle eksempler på, hvordan du kan crashe en MySQL-server i et Linux-miljø. Nogle af dem ville egne sig til f.eks. Amazon RDS-forekomster, hvor du ikke ville have adgang til den underliggende vært.
Dræb, dræb, dræb, dø, dø, dø
Den nemmeste måde at fejle en MySQL-server på er simpelthen at dræbe processen eller værten og ikke give MySQL en chance for at lave en yndefuld nedlukning. For at simulere et mysqld-nedbrud skal du blot sende signal 4, 6, 7, 8 eller 11 til processen:
$ kill -11 $(pidof mysqld)
Når du ser på MySQL-fejlloggen, kan du se følgende linjer:
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...
Du kan også bruge kill -9 (SIGKILL) til at dræbe processen med det samme. Flere detaljer om Linux-signal kan findes her. Alternativt kan du bruge en mere ond måde på hardwaresiden som at trække strømkablet af, trykke på den hårde nulstillingsknap eller bruge et hegn for at STONITH.
Udløser OOM
Populære MySQL i skyen-tilbud som Amazon RDS og Google Cloud SQL har ingen nem måde at crashe dem på. For det første fordi du ikke får adgang til databasen på OS-niveau, og for det andet fordi udbyderen bruger en proprietær patchet MySQL-server. Én måde er at overløbe nogle buffere og lade administratoren uden for hukommelsen (OOM) sparke MySQL-processen ud.
Du kan øge sorteringsbufferstørrelsen til noget større end hvad RAM'en kan håndtere, og skyde en række mysql-sorteringsforespørgsler mod MySQL-serveren. Lad os oprette en tabel med 10 millioner rækker ved hjælp af sysbench på vores Amazon RDS-instans, så vi kan bygge en enorm slags:
$ 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
Skift sort_buffer_size til 5G (vores testinstans er db.t2.micro - 1GB, 1vCPU) ved at gå til Amazon RDS Dashboard -> Parametergrupper -> Opret parametergruppe -> angiv gruppenavnet -> Rediger parametre -> vælg "sort_buffer_size" og angiv værdien som 5368709120.
Anvend parametergruppeændringerne ved at gå til Forekomster -> Forekomsthandling -> Rediger -> Databaseindstillinger -> Databaseparametergruppe -> og vælg vores nyoprettede parametergruppe. Genstart derefter RDS-forekomsten for at anvende ændringerne.
Når du er oppe, skal du bekræfte den nye værdi for sort_buffer_size :
MySQL [(none)]> select @@sort_buffer_size;
+--------------------+
| @@sort_buffer_size |
+--------------------+
| 5368709120 |
+--------------------+
Affyr derefter 48 simple forespørgsler, der kræver sortering fra en klient:
$ 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
Hvis du kører ovenstående på en standard vært, vil du bemærke, at MySQL-serveren vil blive afsluttet, og du kan se følgende linjer vises i OS's syslog eller dmesg:
[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
Med systemd genstartes MySQL eller MariaDB automatisk, det samme gør Amazon RDS. Du kan se, at oppetiden for vores RDS-instans vil blive nulstillet til 0 (under mysqladmin-status), og værdien for 'Seneste gendannelsestid' (under RDS Dashboard) vil blive opdateret til det øjeblik, den gik ned.
Beskadigelse af dataene
InnoDB har sit eget systemtablespace til at gemme dataordbog, buffere og rollback-segmenter i en fil med navnet ibdata1. Den gemmer også det delte tablespace, hvis du ikke konfigurerer innodb_file_per_table (aktiveret som standard i MySQL 5.6.6+). Vi kan bare nulstille denne fil, sende en skriveoperation og tømme tabeller for at crashe mysqld:
# 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'
Når du har sendt en skrivning i fejlloggen, vil du bemærke:
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.
På dette tidspunkt vil mysql hænge, fordi det ikke kan udføre nogen operation, og efter skylningen vil du få "mysqld got signal 11" linjer og mysqld vil lukke ned. For at rydde op skal du fjerne den beskadigede ibdata1, samt ib_logfile*, fordi redologfilerne ikke kan bruges med et nyt system tablespace, der vil blive genereret af mysqld ved næste genstart. Datatab forventes.
For MyISAM-tabeller kan vi rode rundt med .MYD (MyISAM-datafil) og .MYI (MyISAM-indeks) under MySQL datadir. For eksempel erstatter følgende kommando enhver forekomst af streng "F" med "9" inde i en fil:
$ replace F 9 -- /var/lib/mysql/sbtest/sbtest1.MYD
Send derefter nogle skrivninger (f.eks. ved hjælp af sysbench) til måltabellen og udfør skylningen:
mysql> FLUSH TABLE sbtest.sbtest1;
Følgende skulle vises i MySQL-fejlloggen:
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
MyISAM-tabellen vil blive markeret som nedbrudt, og det er nødvendigt at køre REPAIR TABLE-sætning for at gøre den tilgængelig igen.
Begrænsning af ressourcerne
Vi kan også anvende operativsystemets ressourcegrænse på vores mysqld-proces, for eksempel antallet af åbne filbeskrivelser. Brug af variabelen open_file_limit (standard er 5000) tillader mysqld at reservere filbeskrivelser ved hjælp af kommandoen setrlimit(). Du kan indstille denne variabel relativt lille (lige nok til at mysqld kan starte op) og derefter sende flere forespørgsler til MySQL-serveren, indtil den når grænsen.
Hvis mysqld kører på en systemd-server, kan vi indstille den i systemd-enhedsfilen, der er placeret på /usr/lib/systemd/system/mysqld.service, og ændre følgende værdi til noget lavere (systemd standard er 6000):
# Sets open_files_limit
LimitNOFILE = 30
Anvend ændringerne på systemd og genstart MySQL-serveren:
$ systemctl daemon-reload
$ systemctl restart mysqld
Begynd derefter at sende nye forbindelser/forespørgsler, der tæller i forskellige databaser og tabeller, så mysqld skal åbne flere filer. Du vil bemærke følgende fejl:
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!
På dette tidspunkt, når grænsen er nået, vil MySQL fryse, og det vil ikke være i stand til at udføre nogen handling. Når du forsøger at oprette forbindelse, vil du se følgende efter et stykke tid:
$ mysql -uroot -p
ERROR 2013 (HY000): Lost connection to MySQL server at 'reading initial communication packet', system error: 104
Red op med tilladelser
Mysqld-processen kører af "mysql"-bruger, hvilket betyder, at alle filer og mapper, som den skal have adgang til, ejes af mysql-bruger/-gruppe. Ved at rode med tilladelsen og ejerskabet kan vi gøre MySQL-serveren ubrugelig:
$ chown root:root /var/lib/mysql
$ chmod 600 /var/lib/mysql
Generer nogle belastninger til serveren, og opret forbindelse til MySQL-serveren og tøm alle tabeller på disken:
mysql> FLUSH TABLES WITH READ LOCK; UNLOCK TABLES;
I øjeblikket kører mysqld stadig, men det er lidt ubrugeligt. Du kan få adgang til det via en mysql-klient, men du kan ikke udføre nogen handling:
mysql> SHOW DATABASES;
ERROR 1018 (HY000): Can't read dir of '.' (errno: 13 - Permission denied)
Indstil de korrekte tilladelser for at rydde op i rodet:
$ chown mysql:mysql /var/lib/mysql
$ chmod 750 /var/lib/mysql
$ systemctl restart mysqld
Lås den ned
SKYLLEBORD MED LÆSELÅS (FTWRL) kan være ødelæggende under en række forhold. Som for eksempel i en Galera-klynge, hvor alle noder er i stand til at behandle skrivninger, kan du bruge denne sætning til at låse klyngen inde fra en af noderne. Denne erklæring stopper ganske enkelt andre forespørgsler, der skal behandles af mysqld under flushing, indtil låsen frigives, hvilket er meget praktisk til backup-processer (MyISAM-tabeller) og filsystem-øjebliksbilleder.
Selvom denne handling ikke vil gå ned eller ødelægge din databaseserver under låsningen, kan konsekvensen være enorm, hvis sessionen, der holder låsen, ikke frigiver den. For at prøve dette skal du blot:
mysql> FLUSH TABLES WITH READ LOCK;
mysql> exit
Send derefter en masse nye forespørgsler til mysqld'en, indtil den når max_connections værdi. Det er klart, at du ikke kan komme tilbage den samme session som den forrige, når du først er ude. Så låsen vil køre uendeligt, og den eneste måde at frigøre låsen på er ved at dræbe forespørgslen af en anden SUPER privilegeret bruger (ved at bruge en anden session). Eller dræb selve mysqld-processen, eller udfør en hård genstart.
Ansvarsfraskrivelse
Denne blog er skrevet for at give alternativer til sysadmins og DBA'er for at simulere fejlscenarier med MySQL. Prøv ikke disse på din produktionsserver :-)