Du kan ta ner en MySQL-databas på flera sätt. Några uppenbara sätt är att stänga av värden, dra ut strömkabeln, eller hårt döda mysqld-processen med SIGKILL för att simulera ett orent MySQL-avstängningsbeteende. Men det finns också mindre subtila sätt att medvetet krascha din MySQL-server och sedan se vilken typ av kedjereaktion den utlöser. Varför skulle du vilja göra det här? Misslyckanden och återhämtning kan ha många hörnfall, och att förstå dem kan bidra till att minska överraskningsmomentet när saker händer i produktionen. Helst skulle du vilja simulera misslyckanden i en kontrollerad miljö och sedan designa och testa databas-failover-procedurer.
Det finns flera områden i MySQL som vi kan ta itu med, beroende på hur du vill att det ska misslyckas eller krascha. Du kan korrumpera tabellutrymmet, svämma över MySQL-buffertar och cachar, begränsa resurserna för att svälta servern och även bråka med behörigheter. I det här blogginlägget ska vi visa dig några exempel på hur man kraschar en MySQL-server i en Linux-miljö. Några av dem skulle passa för t.ex. Amazon RDS-instanser, där du inte skulle ha tillgång till den underliggande värden.
Döda, döda, döda, dö, dö, dö
Det enklaste sättet att misslyckas med en MySQL-server är att helt enkelt döda processen eller värden, och inte ge MySQL en chans att göra en graciös avstängning. För att simulera en mysqld-krasch, skicka bara signal 4, 6, 7, 8 eller 11 till processen:
$ kill -11 $(pidof mysqld)
När du tittar på MySQL-felloggen kan du se följande rader:
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 också använda kill -9 (SIGKILL) för att döda processen omedelbart. Mer information om Linux-signalen finns här. Alternativt kan du använda ett elakare sätt på hårdvarusidan som att dra av strömkabeln, trycka ner den hårda återställningsknappen eller använda en stängselanordning för att STONITH.
Utlöser OOM
Populära MySQL i molnet-erbjudanden som Amazon RDS och Google Cloud SQL har inget enkelt sätt att krascha dem. För det första för att du inte får någon åtkomst på OS-nivå till databasinstansen, och för det andra för att leverantören använder en proprietär patchad MySQL-server. Ett sätt är att flöda över vissa buffertar och låta managern för out-of-memory (OOM) sparka ut MySQL-processen.
Du kan öka storleken på sorteringsbufferten till något större än vad RAM-minnet klarar av och skjuta ett antal mysql-sorteringsfrågor mot MySQL-servern. Låt oss skapa en tabell med 10 miljoner rader med sysbench på vår Amazon RDS-instans, så att vi kan bygga en enorm sort:
$ 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
Ändra sort_buffer_size till 5G (vår testinstans är db.t2.micro - 1GB, 1vCPU) genom att gå till Amazon RDS Dashboard -> Parametergrupper -> Skapa parametergrupp -> ange gruppnamnet -> Redigera parametrar -> välj "sort_buffer_size" och ange värdet som 5368709120.
Tillämpa parametergruppsändringarna genom att gå till Instanser -> Instansåtgärd -> Ändra -> Databasalternativ -> Databasparametergrupp -> och välj vår nyskapade parametergrupp. Starta sedan om RDS-instansen för att tillämpa ändringarna.
Väl uppe, verifiera det nya värdet för sort_buffer_size :
MySQL [(none)]> select @@sort_buffer_size;
+--------------------+
| @@sort_buffer_size |
+--------------------+
| 5368709120 |
+--------------------+
Skicka sedan 48 enkla frågor som kräver sortering från 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
Om du kör ovanstående på en standardvärd kommer du att märka att MySQL-servern kommer att avslutas och du kan se följande rader visas i operativsystemets 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 kommer MySQL eller MariaDB att startas om automatiskt, så även Amazon RDS. Du kan se att drifttiden för vår RDS-instans kommer att återställas till 0 (under mysqladmin-status), och värdet för 'Senaste återställningstid' (under RDS Dashboard) kommer att uppdateras till det ögonblick då det gick ner.
Förstöra data
InnoDB har sitt eget systemtabellutrymme för att lagra datalexikon, buffertar och återställningssegment i en fil som heter ibdata1. Den lagrar också det delade tabellutrymmet om du inte konfigurerar innodb_file_per_table (aktiverat som standard i MySQL 5.6.6+). Vi kan bara nollställa den här filen, skicka en skrivoperation och spola tabeller för att krascha 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 skickat en skrivning, i felloggen, kommer du att märka:
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.
Vid det här laget kommer mysql att hänga sig eftersom det inte kan utföra någon operation, och efter spolningen kommer du att få "mysqld got signal 11" rader och mysqld kommer att stängas av. För att rensa upp måste du ta bort den skadade ibdata1, samt ib_logfile* eftersom redo-loggfilerna inte kan användas med ett nytt systemtabellutrymme som kommer att genereras av mysqld vid nästa omstart. Dataförlust förväntas.
För MyISAM-tabeller kan vi bråka med .MYD (MyISAM-datafil) och .MYI (MyISAM-index) under MySQL-datakatalogen. Till exempel ersätter följande kommando varje förekomst av strängen "F" med "9" i en fil:
$ replace F 9 -- /var/lib/mysql/sbtest/sbtest1.MYD
Skicka sedan några skrivningar (t.ex. med sysbench) till måltabellen och utför spolningen:
mysql> FLUSH TABLE sbtest.sbtest1;
Följande bör visas i MySQL-felloggen:
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 kommer att markeras som kraschad och att köra REPAIR TABLE-satsen är nödvändig för att göra den tillgänglig igen.
Begränsa resurserna
Vi kan också tillämpa operativsystemets resursgräns på vår mysqld-process, till exempel antalet öppna filbeskrivningar. Genom att använda variabeln open_file_limit (standard är 5000) kan mysqld reservera filbeskrivningar med kommandot setrlimit(). Du kan ställa in den här variabeln relativt liten (precis tillräckligt för att mysqld ska starta) och sedan skicka flera frågor till MySQL-servern tills den når gränsen.
Om mysqld körs i en systemd-server kan vi ställa in den i systemd-enhetsfilen som finns på /usr/lib/systemd/system/mysqld.service och ändra följande värde till något lägre (systemd standard är 6000):
# Sets open_files_limit
LimitNOFILE = 30
Tillämpa ändringarna på systemd och starta om MySQL-servern:
$ systemctl daemon-reload
$ systemctl restart mysqld
Börja sedan skicka nya anslutningar/frågor som räknas i olika databaser och tabeller så mysqld måste öppna flera filer. Du kommer att märka följande fel:
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!
Vid denna tidpunkt, när gränsen nås, kommer MySQL att frysa och det kommer inte att kunna utföra någon operation. När du försöker ansluta, ser du följande efter ett tag:
$ mysql -uroot -p
ERROR 2013 (HY000): Lost connection to MySQL server at 'reading initial communication packet', system error: 104
Bråkar med behörigheter
Mysqld-processen körs av "mysql"-användare, vilket innebär att alla filer och kataloger som den behöver komma åt ägs av mysql-användare/-grupp. Genom att förstöra med tillståndet och ägandet kan vi göra MySQL-servern värdelös:
$ chown root:root /var/lib/mysql
$ chmod 600 /var/lib/mysql
Generera några laddningar till servern och anslut sedan till MySQL-servern och spola alla tabeller till disken:
mysql> FLUSH TABLES WITH READ LOCK; UNLOCK TABLES;
För närvarande körs mysqld fortfarande men det är lite värdelöst. Du kan komma åt den via en mysql-klient men du kan inte göra någon operation:
mysql> SHOW DATABASES;
ERROR 1018 (HY000): Can't read dir of '.' (errno: 13 - Permission denied)
För att rensa upp i röran, ställ in rätt behörigheter:
$ chown mysql:mysql /var/lib/mysql
$ chmod 750 /var/lib/mysql
$ systemctl restart mysqld
Lås ner den
SPOLLBORD MED LÄSSLÅS (FTWRL) kan vara destruktivt under ett antal förhållanden. Som till exempel, i ett Galera-kluster där alla noder kan bearbeta skrivningar, kan du använda denna sats för att låsa klustret inifrån en av noderna. Denna sats stoppar helt enkelt andra frågor som ska behandlas av mysqld under tömningen tills låset släpps, vilket är mycket praktiskt för säkerhetskopieringsprocesser (MyISAM-tabeller) och ögonblicksbilder av filsystemet.
Även om den här åtgärden inte kommer att krascha eller ta ner din databasserver under låsningen, kan konsekvensen bli enorm om sessionen som håller låset inte släpper den. För att prova detta, helt enkelt:
mysql> FLUSH TABLES WITH READ LOCK;
mysql> exit
Skicka sedan en massa nya frågor till mysqld tills den når max_connections värde. Uppenbarligen kan du inte få tillbaka samma session som den förra när du väl är ute. Så låset kommer att köras oändligt och det enda sättet att frigöra låset är genom att döda frågan, av en annan SUPER-behörighetsanvändare (med en annan session). Eller döda själva mysqld-processen, eller utför en hård omstart.
Ansvarsfriskrivning
Den här bloggen är skriven för att ge alternativ till sysadmins och DBA:er för att simulera felscenarier med MySQL. Prova inte dessa på din produktionsserver :-)