Migrate to Percona software for MySQL – an open source, production-ready, and enterprise-grade MySQL alternative.

In this blog post, I’ll look at point-in-time recovery (PITR) options for MySQL, MariaDB and Percona Server for MySQL.
It is a common good practice to extend data safety by having additional measures apart from regular data backups, such as delayed slaves and binary log backups. These two options provide the ability to restore the data to any given point in time, or just revert from some bad accidents. These methods have their limitations of course: delayed slaves only help if a deadly mistake is noticed fast enough, while full point-in-time recovery (PITR) requires the last full backup and binary logs (and therefore usually takes a lot of time).
Alibaba engineers and the MariaDB team implemented an interesting feature in their version of the mysqlbinlog tool: the --flashback option. Based on ROW-based DML events, it can transform the binary log and reverse purposes. That means it can help undo given row changes extremely fast. For instance, it can change DELETE events to INSERTs and vice versa, and it will swap WHERE and SET parts of the UPDATE events. This simple idea can dramatically speed up recovery from certain types of mistakes or disasters.
The question is whether it works with non-MariaDB variants. To verify that, I tested this feature with the latest available Percona Server for MySQL 5.7 (which is fully compatible with upstream MySQL).
|
1 2 3 4 5 6 7 |
master [localhost] {msandbox} ((none)) > select @@version,@@version_comment; +---------------+--------------------------------------------------------+ | @@version | @@version_comment | +---------------+--------------------------------------------------------+ | 5.7.21-20-log | Percona Server (GPL), Release 20, Revision ed217b06ca3 | +---------------+--------------------------------------------------------+ 1 row in set (0.00 sec) |
First, let’s simulate one possible deadly scenario: a forgotten WHERE in DELETE statement:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
master [localhost] {msandbox} ((none)) > select count(*) from test.sbtest1; +----------+ | count(*) | +----------+ | 200 | +----------+ 1 row in set (0.00 sec) master [localhost] {msandbox} ((none)) > delete from test.sbtest1; Query OK, 200 rows affected (0.04 sec) slave1 [localhost] {msandbox} ((none)) > select count(*) from test.sbtest1; +----------+ | count(*) | +----------+ | 0 | +----------+ 1 row in set (0.00 sec) |
So, our data is lost on both the master and slave!
Let’s start by downloading the latest MariaDB server 10.2.x package, which I’m hoping has a mysqlbinlog tool that works with MySQL 5.7, and unpack it to some custom location:
|
1 2 3 |
$ dpkg -x mariadb-server-10.2_10.2.13+maria~wheezy_amd64.deb /opt/maria/ $ /opt/maria/usr/bin/mysqlbinlog --help|grep flash -B, --flashback Flashback feature can rollback you committed data to a |
It has the function we are looking for. Now, we have to find the culprit transaction or set of transactions we want to revert. A simplified example may look like this:
|
1 2 3 |
$ mysqlbinlog -v --base64-output=DECODE-ROWS mysql-bin.000002 > mysql-bin.000002.sql $ less mysql-bin.000002.sql |
By searching through the decoded binary log, we are looking for transactions that have wiped out the table test.sbtest1. It looks like this:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
BEGIN /*!*/; # at 291 #180314 15:30:34 server id 1 end_log_pos 348 CRC32 0x06cd193e Table_map: `test`.`sbtest1` mapped to number 111 # at 348 #180314 15:30:34 server id 1 end_log_pos 8510 CRC32 0x064634c5 Delete_rows: table id 111 ... ### DELETE FROM `test`.`sbtest1` ### WHERE ### @1=200 ... # at 38323 COMMIT/*!*/; |
It is very important to take the proper start and stop positions. We need the ones exactly after BEGIN and before the final COMMIT. Then, let’s test if the tool produces the reverse statements as expected:
|
1 |
$ /opt/maria/usr/bin/mysqlbinlog --flashback -v --base64-output=DECODE-ROWS --start-position=291 --stop-position=38323 mysql-bin.000002 > mysql-bin.000002_flash.sql |
Inside, we find INSERT statements reversing the deletes. Looks good:
|
1 2 3 4 |
### INSERT INTO `test`.`sbtest1` ### SET ### @1=200 ... |
Since we verified the positions are correct, we can prepare a binary log file:
|
1 |
$ /opt/maria/usr/bin/mysqlbinlog --flashback --start-position=291 --stop-position=38323 mysql-bin.000002 > mysql-bin.000002_flash.bin |
and load it back to our master:
|
1 |
master [localhost] {msandbox} (test) > source mysql-bin.000002_flash.bin |
Check the result:
|
1 2 3 4 5 6 7 |
master [localhost] {msandbox} (test) > select count(*) from test.sbtest1; +----------+ | count(*) | +----------+ | 200 | +----------+ 1 row in set (0.00 sec) |
and verify on slaves:
|
1 2 3 4 5 6 7 |
slave1 [localhost] {msandbox} (test) > select count(*) from test.sbtest1; +----------+ | count(*) | +----------+ | 200 | +----------+ 1 row in set (0.00 sec) |
MariaDB has a completely different GTID implementation from MySQL and Percona Server. You can expect problems when decoding incompatible GTID-enabled binary logs with MariaDB.
|
1 |
ERROR 1782 (HY000): @@SESSION.GTID_NEXT cannot be set to ANONYMOUS when @@GLOBAL.GTID_MODE = ON. |
Solutions include disabling GTID temporarily or manually adding GTID information to the flashback log.
Flashback cannot help after DROP/TRUNCATE or other DDL commands. These are not transactional and are not recorded in binary logs. It also does not work with encrypted or compressed binary logs.
Most importantly, the binary log format must be ROW, and row image must be FULL:
|
1 2 3 4 5 6 |
select @@binlog_format,@@binlog_row_image; +-----------------+--------------------+ | @@binlog_format | @@binlog_row_image | +-----------------+--------------------+ | ROW | FULL | +-----------------+--------------------+ |
If these conditions are not met, you must follow the standard point-in-time recovery procedure.
Resources
RELATED POSTS