MariaDB har introducerat en väldigt cool funktion som heter Flashback. Flashback är en funktion som gör att instanser, databaser eller tabeller kan rullas tillbaka till en gammal ögonblicksbild. Traditionellt, för att utföra en punkt-i-tidsåterställning (PITR), skulle man återställa en databas från en säkerhetskopia och spela om de binära loggarna för att rulla fram databastillståndet vid en viss tidpunkt eller position.
Med Flashback kan databasen rullas tillbaka till en tidpunkt i det förflutna, vilket är mycket snabbare om vi bara vill se det förflutna som bara hände för inte så länge sedan. Ibland kan det vara ineffektivt att använda flashback om du vill se en mycket gammal ögonblicksbild av dina data i förhållande till aktuellt datum och tid. Att återställa från en fördröjd slav eller från en säkerhetskopia plus att spela om den binära loggen kan vara de bättre alternativen.
Den här funktionen är endast tillgänglig i MariaDB-klientpaketet, men det betyder inte att vi inte kan använda den med våra MySQL-servrar. Det här blogginlägget visar hur vi kan använda denna fantastiska funktion på en MySQL-server.
MariaDB Flashback-krav
För de som vill använda MariaDB flashback-funktionen ovanpå MySQL kan vi i princip göra följande:
- Aktivera binär logg med följande inställning:
- binlog_format =ROW (standard sedan MySQL 5.7.7).
- binlog_row_image =FULL (standard sedan MySQL 5.6).
- Använd msqlbinlog-verktyget från valfri MariaDB 10.2.4 och senare installation.
- Flashback stöds för närvarande endast över DML-satser (INSERT, DELETE, UPDATE). En kommande version av MariaDB kommer att lägga till stöd för flashback över DDL-satser (DROP, TRUNCATE, ALTER, etc.) genom att kopiera eller flytta den aktuella tabellen till en reserverad och dold databas och sedan kopiera eller flytta tillbaka när du använder flashback.
Återblicken uppnås genom att dra nytta av det befintliga stödet för binära loggar i fullbildsformat, så det stöder alla lagringsmotorer. Observera att flashback-händelserna kommer att lagras i minnet. Därför bör du se till att din server har tillräckligt med minne för den här funktionen.
Hur fungerar MariaDB Flashback?
MariaDBs mysqlbinlog-verktyg kommer med två extra alternativ för detta ändamål:
- -B, --flashback - Flashback-funktionen kan återställa dina engagerade data till en speciell tidpunkt.
- -T, --table=[namn] - Listposter för just denna tabell (endast lokal logg).
Genom att jämföra mysqlbinlog-utgången med och utan --flashback-flaggan kan vi enkelt förstå hur det fungerar. Tänk på att följande sats exekveras på en MariaDB-server:
MariaDB> DELETE FROM sbtest.sbtest1 WHERE id = 1;
Utan flashback-flagga kommer vi att se den faktiska DELETE binlog-händelsen:
$ mysqlbinlog -vv \
--start-datetime="$(date '+%F %T' -d 'now - 10 minutes')" \
--database=sbtest \
--table=sbtest1 \
/var/lib/mysql/binlog.000003
...
# at 453196541
#200227 12:58:18 server id 37001 end_log_pos 453196766 CRC32 0xdaa248ed Delete_rows: table id 238 flags: STMT_END_F
BINLOG '
6rxXXhOJkAAAQwAAAP06AxsAAO4AAAAAAAEABnNidGVzdAAHc2J0ZXN0MQAEAwP+/gTu4P7wAAEB
AAID/P8AFuAQfA==
6rxXXiCJkAAA4QAAAN47AxsAAO4AAAAAAAEAAgAE/wABAAAAVJ4HAHcAODM4Njg2NDE5MTItMjg3
NzM5NzI4MzctNjA3MzYxMjA0ODYtNzUxNjI2NTk5MDYtMjc1NjM1MjY0OTQtMjAzODE4ODc0MDQt
NDE1NzY0MjIyNDEtOTM0MjY3OTM5NjQtNTY0MDUwNjUxMDItMzM1MTg0MzIzMzA7Njc4NDc5Njcz
NzctNDgwMDA5NjMzMjItNjI2MDQ3ODUzMDEtOTE0MTU0OTE4OTgtOTY5MjY1MjAyOTHtSKLa
'/*!*/;
### DELETE FROM `sbtest`.`sbtest1`
### WHERE
### @1=1 /* INT meta=0 nullable=0 is_null=0 */
### @2=499284 /* INT meta=0 nullable=0 is_null=0 */
### @3='83868641912-28773972837-60736120486-75162659906-27563526494-20381887404-41576422241-93426793964-56405065102-33518432330' /* STRING(480) meta=61152 nullable=0 is_null=0 */
### @4='67847967377-48000963322-62604785301-91415491898-96926520291' /* STRING(240) meta=65264 nullable=0 is_null=0 */
...
Genom att utöka ovanstående mysqlbinlog-kommando med --flashback, kan vi se att DELETE-händelsen konverteras till en INSERT-händelse och på liknande sätt till respektive WHERE- och SET-klausuler:
$ mysqlbinlog -vv \
--start-datetime="$(date '+%F %T' -d 'now - 10 minutes')" \
--database=sbtest \
--table=sbtest1 \
/var/lib/mysql/binlog.000003 \
--flashback
...
BINLOG '
6rxXXhOJkAAAQwAAAP06AxsAAO4AAAAAAAEABnNidGVzdAAHc2J0ZXN0MQAEAwP+/gTu4P7wAAEB
AAID/P8AFuAQfA==
6rxXXh6JkAAA4QAAAN47AxsAAO4AAAAAAAEAAgAE/wABAAAAVJ4HAHcAODM4Njg2NDE5MTItMjg3
NzM5NzI4MzctNjA3MzYxMjA0ODYtNzUxNjI2NTk5MDYtMjc1NjM1MjY0OTQtMjAzODE4ODc0MDQt
NDE1NzY0MjIyNDEtOTM0MjY3OTM5NjQtNTY0MDUwNjUxMDItMzM1MTg0MzIzMzA7Njc4NDc5Njcz
NzctNDgwMDA5NjMzMjItNjI2MDQ3ODUzMDEtOTE0MTU0OTE4OTgtOTY5MjY1MjAyOTHtSKLa
'/*!*/;
### INSERT INTO `sbtest`.`sbtest1`
### SET
### @1=1 /* INT meta=0 nullable=0 is_null=0 */
### @2=499284 /* INT meta=0 nullable=0 is_null=0 */
### @3='83868641912-28773972837-60736120486-75162659906-27563526494-20381887404-41576422241-93426793964-56405065102-33518432330' /* STRING(480) meta=61152 nullable=0 is_null=0 */
### @4='67847967377-48000963322-62604785301-91415491898-96926520291' /* STRING(240) meta=65264 nullable=0 is_null=0 */
...
I radbaserad replikering (binlog_format=ROW), innehåller varje radändringshändelse två bilder, en "före"-bild (förutom INSERT) vars kolumner matchas mot när man söker efter raden som ska uppdateras, och en "efter"-bild (förutom DELETE) som innehåller ändringarna. Med binlog_row_image=FULL loggar MariaDB hela rader (det vill säga alla kolumner) för både före- och efterbilderna.
Följande exempel visar binära logghändelser för UPDATE. Tänk på att följande sats exekveras på en MariaDB-server:
MariaDB> UPDATE sbtest.sbtest1 SET k = 0 WHERE id = 5;
När vi tittar på binlog-händelsen för ovanstående uttalande kommer vi att se något i stil med detta:
$ mysqlbinlog -vv \
--start-datetime="$(date '+%F %T' -d 'now - 5 minutes')" \
--database=sbtest \
--table=sbtest1 \
/var/lib/mysql/binlog.000001
...
### UPDATE `sbtest`.`sbtest1`
### WHERE
### @1=5 /* INT meta=0 nullable=0 is_null=0 */
### @2=499813 /* INT meta=0 nullable=0 is_null=0 */
### @3='44257470806-17967007152-32809666989-26174672567-29883439075-95767161284-94957565003-35708767253-53935174705-16168070783' /* STRING(480) meta=61152 nullable=0 is_null=0 */
### @4='34551750492-67990399350-81179284955-79299808058-21257255869' /* STRING(240) meta=65264 nullable=0 is_null=0 */
### SET
### @1=5 /* INT meta=0 nullable=0 is_null=0 */
### @2=0 /* INT meta=0 nullable=0 is_null=0 */
### @3='44257470806-17967007152-32809666989-26174672567-29883439075-95767161284-94957565003-35708767253-53935174705-16168070783' /* STRING(480) meta=61152 nullable=0 is_null=0 */
### @4='34551750492-67990399350-81179284955-79299808058-21257255869' /* STRING(240) meta=65264 nullable=0 is_null=0 */
# Number of rows: 1
...
Med --flashback-flaggan byts "före"-bilden ut mot "efter"-bilden för den befintliga raden:
$ mysqlbinlog -vv \
--start-datetime="$(date '+%F %T' -d 'now - 5 minutes')" \
--database=sbtest \
--table=sbtest1 \
/var/lib/mysql/binlog.000001 \
--flashback
...
### UPDATE `sbtest`.`sbtest1`
### WHERE
### @1=5 /* INT meta=0 nullable=0 is_null=0 */
### @2=0 /* INT meta=0 nullable=0 is_null=0 */
### @3='44257470806-17967007152-32809666989-26174672567-29883439075-95767161284-94957565003-35708767253-53935174705-16168070783' /* STRING(480) meta=61152 nullable=0 is_null=0 */
### @4='34551750492-67990399350-81179284955-79299808058-21257255869' /* STRING(240) meta=65264 nullable=0 is_null=0 */
### SET
### @1=5 /* INT meta=0 nullable=0 is_null=0 */
### @2=499813 /* INT meta=0 nullable=0 is_null=0 */
### @3='44257470806-17967007152-32809666989-26174672567-29883439075-95767161284-94957565003-35708767253-53935174705-16168070783' /* STRING(480) meta=61152 nullable=0 is_null=0 */
### @4='34551750492-67990399350-81179284955-79299808058-21257255869' /* STRING(240) meta=65264 nullable=0 is_null=0 */
...
Vi kan sedan omdirigera flashback-utgången till MySQL-klienten och på så sätt rulla tillbaka databasen eller tabellen till den tidpunkt som vi vill. Fler exempel visas i nästa avsnitt.
MariaDB har en dedikerad kunskapsbassida för den här funktionen. Kolla in MariaDB Flashbacks kunskapsbassida.
MariaDB Flashback med MySQL
För att ha flashback-förmågan för MySQL måste man göra följande:
- Kopiera mysqlbinlog-verktyget från valfri MariaDB-server (10.2.4 eller senare).
- Inaktivera MySQL GTID innan du använder Flashback SQL-filen. Globala variabler gtid_mode och enforce_gtid_consistency kan ställas in under körning sedan MySQL 5.7.5.
Anta att vi har följande enkla MySQL 8.0-replikeringstopologi:
I det här exemplet kopierade vi mysqlbinlog-verktyget från den senaste MariaDB 10.4 på en av våra MySQL 8.0-slavar (slave2):
(mariadb-server)$ scp /bin/mysqlbinlog [email protected]:/root/
(slave2-mysql8)$ ls -l /root/mysqlbinlog
-rwxr-xr-x. 1 root root 4259504 Feb 27 13:44 /root/mysqlbinlog
Vår MariaDB:s mysqlbinlog-verktyg finns nu på /root/mysqlbinlog på slave2. På MySQL-mastern körde vi följande katastrofala uttalande:
mysql> DELETE FROM sbtest1 WHERE id BETWEEN 5 AND 100;
Query OK, 96 rows affected (0.01 sec)
96 rader raderades i ovanstående uttalande. Vänta ett par sekunder för att låta händelserna replikera från master till alla slavar innan vi kan försöka hitta binlogpositionen för den katastrofala händelsen på slavservern. Det första steget är att hämta alla binära loggar på den servern:
mysql> SHOW BINARY LOGS;
+---------------+-----------+-----------+
| Log_name | File_size | Encrypted |
+---------------+-----------+-----------+
| binlog.000001 | 850 | No |
| binlog.000002 | 18796 | No |
+---------------+-----------+-----------+
Vår katastrofala händelse borde finnas i binlog.000002, den senaste binära loggen på denna server. Vi kan sedan använda MariaDB:s mysqlbinlog-verktyg för att hämta alla binlog-händelser för tabellen sbtest1 sedan 10 minuter sedan:
(slave2-mysql8)$ /root/mysqlbinlog -vv \
--start-datetime="$(date '+%F %T' -d 'now - 10 minutes')" \
--database=sbtest \
--table=sbtest1 \
/var/lib/mysql/binlog.000002
...
# at 195
#200228 15:09:45 server id 37001 end_log_pos 281 CRC32 0x99547474 Ignorable
# Ignorable event type 33 (MySQL Gtid)
# at 281
#200228 15:09:45 server id 37001 end_log_pos 353 CRC32 0x8b12bd3c Query thread_id=19 exec_time=0 error_code=0
SET TIMESTAMP=1582902585/*!*/;
SET @@session.pseudo_thread_id=19/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1, @@session.check_constraint_checks=1/*!*/;
SET @@session.sql_mode=524288/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
SET @@session.character_set_client=255,@@session.collation_connection=255,@@session.collation_server=255/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
BEGIN
/*!*/;
# at 353
#200228 15:09:45 server id 37001 end_log_pos 420 CRC32 0xe0e44a1b Table_map: `sbtest`.`sbtest1` mapped to number 92
# at 420
# at 8625
# at 16830
#200228 15:09:45 server id 37001 end_log_pos 8625 CRC32 0x99b1a8fc Delete_rows: table id 92
#200228 15:09:45 server id 37001 end_log_pos 16830 CRC32 0x89496a07 Delete_rows: table id 92
#200228 15:09:45 server id 37001 end_log_pos 18765 CRC32 0x302413b2 Delete_rows: table id 92 flags: STMT_END_F
För att enkelt söka efter binlogpositionsnumret, var uppmärksam på raderna som börjar med "# vid ". Från ovanstående rader kan vi se att DELETE-händelsen ägde rum vid position 281 inuti binlog.000002 (startar vid "# vid 281"). Vi kan också hämta binlog-händelserna direkt inuti en MySQL-server:
mysql> SHOW BINLOG EVENTS IN 'binlog.000002';
+---------------+-------+----------------+-----------+-------------+-------------------------------------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+---------------+-------+----------------+-----------+-------------+-------------------------------------------------------------------+
| binlog.000002 | 4 | Format_desc | 37003 | 124 | Server ver: 8.0.19, Binlog ver: 4 |
| binlog.000002 | 124 | Previous_gtids | 37003 | 195 | 0d98d975-59f8-11ea-bd30-525400261060:1 |
| binlog.000002 | 195 | Gtid | 37001 | 281 | SET @@SESSION.GTID_NEXT= '0d98d975-59f8-11ea-bd30-525400261060:2' |
| binlog.000002 | 281 | Query | 37001 | 353 | BEGIN |
| binlog.000002 | 353 | Table_map | 37001 | 420 | table_id: 92 (sbtest.sbtest1) |
| binlog.000002 | 420 | Delete_rows | 37001 | 8625 | table_id: 92 |
| binlog.000002 | 8625 | Delete_rows | 37001 | 16830 | table_id: 92 |
| binlog.000002 | 16830 | Delete_rows | 37001 | 18765 | table_id: 92 flags: STMT_END_F |
| binlog.000002 | 18765 | Xid | 37001 | 18796 | COMMIT /* xid=171006 */ |
+---------------+-------+----------------+-----------+-------------+-------------------------------------------------------------------+
9 rows in set (0.00 sec)
Vi kan nu bekräfta att position 281 är där vi vill att våra data ska återgå till. Vi kan sedan använda flaggan --start-position för att generera korrekta flashback-händelser. Lägg märke till att vi utelämnar "-vv"-flaggan och lägg till --flashback-flaggan:
(slave2-mysql8)$ /root/mysqlbinlog \
--start-position=281 \
--database=sbtest \
--table=sbtest1 \
/var/lib/mysql/binlog.000002 \
--flashback > /root/flashback.binlog
Flashback.binlog innehåller alla nödvändiga händelser för att ångra alla ändringar som har skett i tabellen sbtest1 på denna MySQL-server. Eftersom detta är en slavnod för ett replikeringskluster, måste vi bryta replikeringen på den valda slaven (slave2) för att kunna använda den för flashbacksyften. För att göra detta måste vi stoppa replikeringen på den valda slaven, ställa in MySQL GTID till ON_PERMISSIVE och göra slaven skrivbar:
mysql> STOP SLAVE;
SET GLOBAL gtid_mode = ON_PERMISSIVE;
SET GLOBAL enforce_gtid_consistency = OFF;
SET GLOBAL read_only = OFF;
I det här läget är slave2 inte en del av replikeringen och vår topologi ser ut så här:
Importera flashbacken via mysql-klienten och vi vill inte att denna ändring ska vara registreras i MySQL binär logg:
(slave2-mysql8)$ mysql -uroot -p --init-command='SET sql_log_bin=0' sbtest < /root/flashback.binlog
Vi kan sedan se alla raderade rader, vilket bevisas av följande påstående:
mysql> SELECT COUNT(id) FROM sbtest1 WHERE id BETWEEN 5 and 100;
+-----------+
| COUNT(id) |
+-----------+
| 96 |
+-----------+
1 row in set (0.00 sec)
Vi kan sedan skapa en SQL-dumpfil för tabellen sbtest1 för vår referens:
(slave2-mysql8)$ mysqldump -uroot -p --single-transaction sbtest sbtest1 > sbtest1_flashbacked.sql
När flashback-operationen är klar kan vi återförena slavnoden tillbaka till replikeringskedjan. Men först måste vi föra tillbaka databasen till ett konsekvent tillstånd, genom att spela upp alla händelser från den position vi hade flashback. Glöm inte att hoppa över binär loggning eftersom vi inte vill "skriva" på slaven och riskera oss själva med felaktiga transaktioner:
(slave2-mysql8)$ /root/mysqlbinlog \
--start-position=281 \
--database=sbtest \
--table=sbtest1 \
/var/lib/mysql/binlog.000002 | mysql -uroot -p --init-command='SET sql_log_bin=0' sbtest
Slutligen, förbered noden tillbaka till sin roll som MySQL-slav och starta replikeringen:
mysql> SET GLOBAL read_only = ON;
SET GLOBAL enforce_gtid_consistency = ON;
SET GLOBAL gtid_mode = ON;
START SLAVE;
Verifiera att slavnoden replikerar korrekt:
mysql> SHOW SLAVE STATUS\G
...
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
...
Vid denna tidpunkt har vi återanslutit slaven tillbaka till replikeringskedjan och vår topologi är nu tillbaka till sitt ursprungliga tillstånd:
Ropa ut till MariaDB-teamet för att de introducerar denna häpnadsväckande funktion!