MariaDB wprowadziła bardzo fajną funkcję o nazwie Flashback. Flashback to funkcja, która pozwoli na przywrócenie instancji, baz danych lub tabel do starej migawki. Tradycyjnie, aby wykonać odzyskiwanie do punktu w czasie (PITR), należy przywrócić bazę danych z kopii zapasowej i odtworzyć logi binarne w celu przewinięcia stanu bazy danych w określonym czasie lub pozycji.
Dzięki Flashback bazę danych można cofnąć do punktu z przeszłości, co jest znacznie szybsze, jeśli chcemy tylko zobaczyć przeszłość, która wydarzyła się niedawno. Czasami użycie retrospekcji może być nieefektywne, jeśli chcesz zobaczyć bardzo starą migawkę danych w odniesieniu do bieżącej daty i godziny. Przywracanie z opóźnionego urządzenia podrzędnego lub z kopii zapasowej plus odtwarzanie dziennika binarnego może być lepszymi opcjami.
Ta funkcja jest dostępna tylko w pakiecie klienta MariaDB, ale to nie znaczy, że nie możemy jej używać z naszymi serwerami MySQL. Ten wpis na blogu pokazuje, jak możemy wykorzystać tę niesamowitą funkcję na serwerze MySQL.
Wymagania dotyczące Flashback MariaDB
Dla tych, którzy chcą korzystać z funkcji flashback MariaDB na bazie MySQL, możemy zasadniczo wykonać następujące czynności:
- Włącz dziennik binarny z następującym ustawieniem:
- binlog_format =ROW (domyślnie od wersji MySQL 5.7.7).
- binlog_row_image =FULL (domyślnie od MySQL 5.6).
- Użyj narzędzia msqlbinlog z dowolnej instalacji MariaDB 10.2.4 i nowszych.
- Flashback jest obecnie obsługiwany tylko przez instrukcje DML (INSERT, DELETE, UPDATE). Nadchodząca wersja MariaDB doda obsługę flashback przez instrukcje DDL (DROP, TRUNCATE, ALTER itp.), kopiując lub przenosząc bieżącą tabelę do zarezerwowanej i ukrytej bazy danych, a następnie kopiując lub cofając się podczas korzystania z flashback.
Retrospekcja jest osiągana dzięki wykorzystaniu istniejącej obsługi dzienników binarnych w pełnym formacie obrazu, dzięki czemu obsługuje wszystkie silniki pamięci masowej. Pamiętaj, że zdarzenia retrospekcji będą przechowywane w pamięci. Dlatego powinieneś upewnić się, że twój serwer ma wystarczającą ilość pamięci dla tej funkcji.
Jak działa MariaDB Flashback?
Narzędzie MariaDB mysqlbinlog zawiera dwie dodatkowe opcje do tego celu:
- -B, --flashback - Funkcja Flashback może cofnąć Twoje zatwierdzone dane do specjalnego punktu czasowego.
- -T, --table=[nazwa] — Lista wpisów tylko dla tej tabeli (tylko dziennik lokalny).
Porównując wyjście mysqlbinlog z flagą --flashback i bez niej, możemy łatwo zrozumieć, jak to działa. Rozważmy, że na serwerze MariaDB wykonywane jest następujące polecenie:
MariaDB> DELETE FROM sbtest.sbtest1 WHERE id = 1;
Bez flagi flashback zobaczymy rzeczywiste zdarzenie DELETE binlog:
$ 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 */
...
Rozszerzając powyższe polecenie mysqlbinlog z opcją --flashback, widzimy, że zdarzenie DELETE jest konwertowane na zdarzenie INSERT i podobnie do odpowiednich klauzul WHERE i SET:
$ 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 */
...
W replikacji opartej na wierszach (binlog_format=ROW) każde zdarzenie zmiany wiersza zawiera dwa obrazy, obraz „przed” (z wyjątkiem INSERT), do którego kolumny są dopasowywane podczas wyszukiwania wiersza do aktualizacji, oraz obraz „po” (z wyjątkiem DELETE) zawierający zmiany. Gdy binlog_row_image=FULL, MariaDB rejestruje pełne wiersze (czyli wszystkie kolumny) zarówno dla obrazu przed, jak i po.
Poniższy przykład przedstawia zdarzenia dziennika binarnego dla aktualizacji. Rozważmy, że na serwerze MariaDB wykonywane jest następujące polecenie:
MariaDB> UPDATE sbtest.sbtest1 SET k = 0 WHERE id = 5;
Patrząc na zdarzenie binlog dla powyższego oświadczenia, zobaczymy coś takiego:
$ 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
...
W przypadku flagi --flashback obraz „przed” jest zamieniany na obraz „po” istniejącego wiersza:
$ 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 */
...
Możemy wtedy przekierować wyjście flashback do klienta MySQL, cofając w ten sposób bazę danych lub tabelę do żądanego punktu w czasie. Więcej przykładów pokazano w następnych sekcjach.
MariaDB ma dedykowaną stronę bazy wiedzy dla tej funkcji. Sprawdź stronę bazy wiedzy MariaDB Flashback.
Flashback MariaDB z MySQL
Aby mieć możliwość retrospekcji dla MySQL, należy wykonać następujące czynności:
- Skopiuj narzędzie mysqlbinlog z dowolnego serwera MariaDB (10.2.4 lub nowszego).
- Wyłącz identyfikator GTID MySQL przed zastosowaniem pliku flashback SQL. Zmienne globalne gtid_mode i force_gtid_consistency można ustawiać w środowisku wykonawczym od wersji MySQL 5.7.5.
Załóżmy, że mamy następującą prostą topologię replikacji MySQL 8.0:
W tym przykładzie skopiowaliśmy narzędzie mysqlbinlog z najnowszej wersji MariaDB 10.4 na jeden z naszych urządzeń podrzędnych MySQL 8.0 (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
Narzędzie mysqlbinlog naszej MariaDB znajduje się teraz w /root/mysqlbinlog na slave2. Na wzorcu MySQL wykonaliśmy następującą katastrofalną instrukcję:
mysql> DELETE FROM sbtest1 WHERE id BETWEEN 5 AND 100;
Query OK, 96 rows affected (0.01 sec)
W powyższym oświadczeniu skreślono 96 wierszy. Poczekaj kilka sekund, aby zdarzenia zreplikowały się z urządzenia głównego do wszystkich urządzeń podrzędnych, zanim będziemy mogli spróbować znaleźć pozycję dziennika binarnego katastrofalnego zdarzenia na serwerze podrzędnym. Pierwszym krokiem jest pobranie wszystkich dzienników binarnych na tym serwerze:
mysql> SHOW BINARY LOGS;
+---------------+-----------+-----------+
| Log_name | File_size | Encrypted |
+---------------+-----------+-----------+
| binlog.000001 | 850 | No |
| binlog.000002 | 18796 | No |
+---------------+-----------+-----------+
Nasze katastrofalne wydarzenie powinno istnieć w binlog.000002, najnowszym dzienniku binarnym na tym serwerze. Następnie możemy użyć narzędzia MariaDB mysqlbinlog, aby pobrać wszystkie zdarzenia binlog dla tabeli sbtest1 sprzed 10 minut:
(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
Aby łatwo wyszukać numer pozycji w binlogu, zwróć uwagę na wiersze zaczynające się od „# at”. Z powyższych wierszy widzimy, że zdarzenie DELETE miało miejsce na pozycji 281 wewnątrz binlog.000002 (zaczyna się od „# w 281”). Możemy również pobrać zdarzenia binlog bezpośrednio z serwera MySQL:
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)
Możemy teraz potwierdzić, że pozycja 281 to miejsce, do którego chcemy przywrócić nasze dane. Następnie możemy użyć flagi --start-position do wygenerowania dokładnych zdarzeń retrospekcji. Zauważ, że pomijamy flagę „-vv” i dodaj flagę --flashback:
(slave2-mysql8)$ /root/mysqlbinlog \
--start-position=281 \
--database=sbtest \
--table=sbtest1 \
/var/lib/mysql/binlog.000002 \
--flashback > /root/flashback.binlog
Plik flashback.binlog zawiera wszystkie zdarzenia wymagane do cofnięcia wszystkich zmian dokonanych w tabeli sbtest1 na tym serwerze MySQL. Ponieważ jest to węzeł podrzędny klastra replikacji, musimy przerwać replikację na wybranym podrzędnym (slave2), aby użyć go do celów retrospekcji. Aby to zrobić, musimy zatrzymać replikację na wybranym urządzeniu podrzędnym, ustawić GTID MySQL na ON_PERMISSIVE i umożliwić zapis do urządzenia podrzędnego:
mysql> STOP SLAVE;
SET GLOBAL gtid_mode = ON_PERMISSIVE;
SET GLOBAL enforce_gtid_consistency = OFF;
SET GLOBAL read_only = OFF;
W tym momencie slave2 nie jest częścią replikacji, a nasza topologia wygląda tak:
Zaimportuj flashback przez klienta mysql i nie chcemy, aby ta zmiana została zapisane w logu binarnym MySQL:
(slave2-mysql8)$ mysql -uroot -p --init-command='SET sql_log_bin=0' sbtest < /root/flashback.binlog
Możemy wtedy zobaczyć wszystkie usunięte wiersze, o czym świadczy następująca instrukcja:
mysql> SELECT COUNT(id) FROM sbtest1 WHERE id BETWEEN 5 and 100;
+-----------+
| COUNT(id) |
+-----------+
| 96 |
+-----------+
1 row in set (0.00 sec)
Następnie możemy utworzyć plik zrzutu SQL dla tabeli sbtest1 w celach informacyjnych:
(slave2-mysql8)$ mysqldump -uroot -p --single-transaction sbtest sbtest1 > sbtest1_flashbacked.sql
Po zakończeniu operacji flashback możemy ponownie dołączyć węzeł podrzędny z powrotem do łańcucha replikacji. Ale najpierw musimy przywrócić bazę danych do spójnego stanu, odtwarzając wszystkie zdarzenia, zaczynając od pozycji, którą prześledziliśmy. Nie zapomnij pominąć rejestrowania binarnego, ponieważ nie chcemy „zapisywać” na urządzeniu podrzędnym i ryzykować błędnymi transakcjami:
(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
Na koniec przygotuj węzeł z powrotem do jego roli jako niewolnika MySQL i rozpocznij replikację:
mysql> SET GLOBAL read_only = ON;
SET GLOBAL enforce_gtid_consistency = ON;
SET GLOBAL gtid_mode = ON;
START SLAVE;
Sprawdź, czy węzeł podrzędny replikuje się prawidłowo:
mysql> SHOW SLAVE STATUS\G
...
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
...
W tym momencie ponownie przyłączyliśmy urządzenie podrzędne z powrotem do łańcucha replikacji, a nasza topologia wróciła do swojego pierwotnego stanu:
Wołajmy zespołowi MariaDB za wprowadzenie tej zdumiewającej funkcji!