Skip to content

39 数据误操作了如何快速恢复?

你好,我是俊达。

上一讲中,我们知道了如何使用Xtrabackup来备份数据库和恢复数据库。不管是全量备份,还是增量备份,实际上都只能将数据库恢复到一个固定的时间点,这个时间点就是Xtrabackup备份完成的那个时刻。

但是在现实中,数据恢复的要求往往会更高。假设我们每天凌晨都会备份数据库。某一天下午,由于误操作,某一个重要的业务表的数据被清空了。使用凌晨的备份文件,只能恢复一部分数据。从凌晨到误操作发生的那一刻,这个表上进行了很多DML操作,这部分数据如何恢复呢?

这一讲我们就来解决这个问题。根据误操作的不同情况,我们可以选择不同方法来恢复数据。

Binlog备份

为了将数据库恢复到任意一个时间点,我们需要将Binlog也备份起来。如果Binlog没有备份,同时你又把Binlog删除了,那么就可能无法将数据恢复到这些Binlog覆盖的时间点。

备份Binlog实际上比较简单,一个Binlog,一旦完成切换,里面的内容就不会发生变化。你可以使用操作系统的命令,比如cp,将除了最后一个binlog之外其他Binlog,拷贝到备份空间。使用这种方式,无法实时备份最新的那个Binlog文件。极端情况下,如果这个时候数据库所在的服务器崩溃了,而且无法启动,那么你可能会丢失最后一个Binlog中的数据。

命令行工具mysqlbinlog提供了一个功能,可以实时接收远程数据库上生成的Binlog事件。

mysqlbinlog \
    --read-from-remote-server \
  --host=172.16.121.234 \
  --port=3307 \
    --user=backup \
    --password=somepass \
    --protocol=tcp \
    --stop-never \
    --raw \
    --result-file=/path/to/binlog-backup/ \
  mysql-binlog.00000N

mysqlbinlog的命令行参数中,加上read-from-remote-server后,可以模拟IO线程,连接到数据库,执行BINLOG_DUMP命令,接收Binlog。加上参数stop-never,读取完当前所有的Binlog后,也不会退出,而是继续等待主库生成新的Binlog事件。

使用mysqlbinlog备份远程的Binlog时,一般要加上raw参数。你可以将参数result-file设置为一个目录,这样Binlog会写到这个目录中,如果不加这个参数,默认会将Binlog文件保存到当前目录下。

最后一个参数(mysql-binlog.00000N)是开始读取的第一个Binlog的文件名。如果发生一些异常,如网络超时,或Dump线程被Kill了,mysqlbinlog命令会异常退出。如果mysqlbinlog命令异常退出了,你可以先看一下最后备份的那个Binlog文件,然后再重新从这个文件开始,备份Binlog。

恢复到指定时间点

只要有全量备份,以及全量备份后的所有binlog,就能将数据库恢复到全量备份之后的任意时间点。

图片

看上面这个图,如果在T2时间点发生了数据丢失故障,就可以找一个在T2时间之前完成的全量备份。假设在时间点T0有一个全量备份,T1有一个增量备份,就可以先恢复全量备份和增量备份,然后再应用T1到T2之间的Binlog。如果没有增量备份,也可以恢复全量备份后,应用T0到T2之间的Binlog。这样都能将数据恢复到发生故障前的那个时间点。

找到时间点对应的binlog

时间点恢复具体怎么操作呢?首先要定位该时间点对应的binlog位点(binlog文件和文件内的偏移量)。每个binlog头部都记录了binlog产生的时间,我们可以使用mysqlbinlog工具解析binlog,查看binlog的第一个event的时间。

# mysqlbinlog -v binlog.000021 | head
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#230625 16:44:06 server id 23480  end_log_pos 126 CRC32 0x245d7ed7  Start: binlog v 4, server v 8.0.32 created 230625 16:44:06

如果我们需要恢复到某个时间点T,那么我们需要找的binlog开始时间不大于T,并且该binlog的下一个binlog的开始时间大于T。

这里提供一个python的脚本,可以批量查看binlog时间。

import sys
import struct

if len(sys.argv) >= 2:
   pattern = sys.argv[1]
else:
   pattern = 'mysql-bin.[0-9]*'

print ('binlog pattern: %s' % pattern)

def parse_binlog_header(filename):
    with open(filename, 'rb') as f:
        data = f.read(8)
        return struct.unpack('i', data[4:])[0]

def main():

    import glob
    from datetime import datetime
    for f in sorted(glob.glob(pattern), key=lambda x: int(x.split('.')[-1])):
        ts = parse_binlog_header(f)
        print f, ts, datetime.fromtimestamp(ts)

if __name__ == '__main__':
    main()

使用脚本,传入binlog匹配模式,显示binlog时间:

# python parse_binlog_time.py  'binlog/binlog.[0-9]*'
binlog pattern: binlog/binlog.[0-9]*
binlog/binlog.000001 1686640790 2023-06-13 15:19:50
binlog/binlog.000002 1686647377 2023-06-13 17:09:37
binlog/binlog.000003 1686647391 2023-06-13 17:09:51
......
binlog/binlog.000020 1687682137 2023-06-25 16:35:37
binlog/binlog.000021 1687682646 2023-06-25 16:44:06
binlog/binlog.000022 1687683127 2023-06-25 16:52:07

我们的全量备份binlog位点是binlog.000020。

# cat xtrabackup_binlog_info
binlog.000020   610 58224b02-09b7-11ee-90bd-fab81f64ee00:1-13191,7caa9a48-b325-11ed-8541-fab81f64ee00:1-27

假设我们希望将数据库恢复到2023-06-25 16:45:00,那么根据各个binlog的时间信息,我们需要恢复到binlog.000021,从这个binlog中找到16:45:00对应的位点。

# mysqlbinlog --stop-datetime="2023-06-25 16:45:01" binlog/binlog.000021 | grep -A 1 "^# at" | tail -2

# at 340009
#230625 16:45:00 server id 23480  end_log_pos 340040 CRC32 0xa1841663   Xid = 88279

我们需要应用binlog.000021偏移量340040之前的binlog。

接下来要解决怎么应用Binlog的问题。MySQL提供了命令行工具mysqlbinlog,可以解析Binlog,解析出来后,可以直接发送给MySQL执行。这可能也是平时比较常用的一个方法。更好的方法,是利用MySQL的复制模块来应用Binlog。我们来看一下这两种方法分别是怎么操作的。

方法一:使用mysqlbinlog解析binlog并执行

从前面的步骤,我们得到了需要执行的binlog。

  • binlog开始位点:binlog.000020,偏移量610。

  • binlog结束位点:binlog.000021,偏移量340040。

依次使用mysqlbinlog解析binlog,并发送给mysql执行。执行第一个binlog时指定参数start-position,执行最后一个binlog时,指定参数stop-position。

使用下面的命令执行第一个binlog。

mysqlbinlog --start-position=610 binlog.000020 | mysql -uroot -h127.0.0.1 -P6380 -uroot -pabc123

执行中间的binlog,如果binlog数量比较多,你可能需要写一个脚本来处理。当然,这个测试案例中只有2个binlog。

使用下面的命令执行最后一个binlog。

mysqlbinlog --stop-position=340040 binlog.000021 | mysql -uroot -h127.0.0.1 -P6380 -uroot -pabc123

binlog执行完成后,校验一下数据。这里我们查看最后一个应用的binlog事件。

 mysqlbinlog -v --stop-position=340040 binlog.000021 | tail -30
# at 339746
#230625 16:45:00 server id 23480  end_log_pos 339817 CRC32 0x89660c48   Query   thread_id=29218 exec_time=0 error_code=0
SET TIMESTAMP=1687682700/*!*/;
BEGIN
/*!*/;
# at 339817
#230625 16:45:00 server id 23480  end_log_pos 339880 CRC32 0xd5fb3c23   Table_map: `demo`.`last_gtid` mapped to number 209
# has_generated_invisible_primary_key=0
# at 339880
#230625 16:45:00 server id 23480  end_log_pos 340009 CRC32 0x8153feb1   Write_rows: table id 209 flags: STMT_END_F

BINLOG '
jP6XZBO4WwAAPwAAAKgvBQAAANEAAAAAAAEABGRlbW8ACWxhc3RfZ3RpZAACAw8CoA8CAQEAAgP8
/wAjPPvV
jP6XZB64WwAAgQAAACkwBQAAANEAAAAAAAEAAgAC/wBsaAAAVwA1ODIyNGIwMi0wOWI3LTExZWUt
OTBiZC1mYWI4MWY2NGVlMDA6MS0yOTExMCwKN2NhYTlhNDgtYjMyNS0xMWVkLTg1NDEtZmFiODFm
NjRlZTAwOjEtMjex/lOB
'/*!*/;
### INSERT INTO `demo`.`last_gtid`
### SET
###   @1=26732
###   @2='58224b02-09b7-11ee-90bd-fab81f64ee00:1-29110,\n7caa9a48-b325-11ed-8541-fab81f64ee00:1-27'

查看数据,验证数据是否恢复到了正确的时间点。

mysql> select * from demo.last_gtid order by id desc limit 1;
+-------+-----------------------------------------------------------------------------------------+
| id    | gtid                                                                                    |
+-------+-----------------------------------------------------------------------------------------+
| 26732 | 58224b02-09b7-11ee-90bd-fab81f64ee00:1-29110,
7caa9a48-b325-11ed-8541-fab81f64ee00:1-27 |
+-------+-----------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

方法二:使用MySQL原生复制功能实现时间点恢复

虽然使用mysqlbinlog解析并执行binlog,是实现mysql时间点恢复的一种常用的方法,但是这么做其实有一些缺点。

  • 先解析Binlog,再发送给MySQL执行,性能不够好,事务只能单线程应用。

  • 如果中间某个Binlog执行时出错了,脚本中还要做好错误处理。

在正式环境中,我更建议使用下面这种方法,利用MySQL的数据复制功能来实现时间点恢复。

操作方法其实很简单。

  1. 启动一个空的mysql实例,将需要恢复的binlog注册到这个实例中。

  2. 将需要恢复数据的那个实例,作为备库,从步骤1创建的binlog实例中复制数据。

启动binlog实例

先初始化一个空白实例。

## 创建相关目录
mkdir -p /data/servebinlog/{data,log,binlog,relaylog,tmp,run}

## 准备my.cnf

## 初始化数据库
/opt/mysql/bin/mysqld --defaults-file=/data/servebinlog/my.cnf --initialize

tail -1 /data/servebinlog/log/alert.log
2023-06-26T08:54:33.758630Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: ar2sWPCf4.++

## 修改文件owner,并启动实例
chown -R mysql:mysql /data/servebinlog/

mysqld_safe --defaults-file=/data/servebinlog/my.cnf &

## 修改默认密码,创建复制账号
mysql -uroot -par2sWPCf4.++ -h127.0.0.1 -P16380

mysql> alter user 'root'@'localhost' identified by 'abc123';
Query OK, 0 rows affected (0.01 sec)

mysql> create user 'rep'@'%' identified by 'rep123';
Query OK, 0 rows affected (0.02 sec)

mysql> grant replication slave on *.* to 'rep'@'%';
Query OK, 0 rows affected (0.01 sec)

注册binlog。将需要的binlog复制到binlog实例的目录中,并编辑binlog.index文件,重启binlog实例。

## 停实例
mysqladmin -h127.0.0.1 -P16380 -uroot -pabc123 shutdown

## 清空原有binlog,将需要的binlog复制到指定目录
rm /data/servebinlog/binlog/binlog.00000*
cp /data/mysql8.0/binlog/binlog.0000{20,21,22} /data/servebinlog/binlog/

## 编辑binlog.index,最终内容如下
# cat /data/servebinlog/binlog/binlog.index
/data/servebinlog/binlog/binlog.000020
/data/servebinlog/binlog/binlog.000021
/data/servebinlog/binlog/binlog.000022

## 启动实例
chown -R mysql:mysql /data/servebinlog/
mysqld_safe --defaults-file=/data/servebinlog/my.cnf &

到这一步,binlog实例就准备完成了。

mysql> show binary logs;
+---------------+-----------+-----------+
| Log_name      | File_size | Encrypted |
+---------------+-----------+-----------+
| binlog.000020 |   5598638 | No        |
| binlog.000021 |   4751928 | No        |
| binlog.000022 |   3944712 | No        |
| binlog.000023 |       237 | No        |
+---------------+-----------+-----------+

mysql> select @@gtid_purged\G
*************************** 1. row ***************************
@@gtid_purged: 0b06b27c-13ff-11ee-8d21-fab81f64ee00:1-3,
58224b02-09b7-11ee-90bd-fab81f64ee00:1-13191,
7caa9a48-b325-11ed-8541-fab81f64ee00:27
1 row in set (0.00 sec)

mysql> select @@gtid_executed\G
*************************** 1. row ***************************
@@gtid_executed: 0b06b27c-13ff-11ee-8d21-fab81f64ee00:1-3,
58224b02-09b7-11ee-90bd-fab81f64ee00:1-51514,
7caa9a48-b325-11ed-8541-fab81f64ee00:27
1 row in set (0.00 sec)

从binlog实例复制数据

先查看全备的binlog位点。

# cat xtrabackup_binlog_info
binlog.000020   610 58224b02-09b7-11ee-90bd-fab81f64ee00:1-13191,7caa9a48-b325-11ed-8541-fab81f64ee00:1-27

到需要进行时间点恢复的实例上执行下面这些命令,建立数据复制通道,使用start slave until命令将数据恢复到指定的Binlog位点。

mysql> reset slave all;
Query OK, 0 rows affected, 1 warning (0.03 sec)

mysql> change master to master_host='172.16.121.234', master_port=16380, master_user='rep', master_password='rep123', get_master_public_key=1, master_log_file='binlog.000020', master_log_pos=610;
Query OK, 0 rows affected, 9 warnings (0.13 sec)

mysql> start slave until master_log_file='binlog.000021', master_log_pos=340009;
Query OK, 0 rows affected, 3 warnings (0.03 sec)

等待SQL线程执行完成,执行完成的标志是Slave_SQL_Running=No,并且Relay_Master_Log_File和Exec_Master_Log_Pos和start slave until命令中指定的位点一样。

mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for source to send event
                  Master_Host: 172.16.121.234
                  Master_User: rep
                  Master_Port: 16380
                Connect_Retry: 60
              Master_Log_File: binlog.000023
          Read_Master_Log_Pos: 237
               Relay_Log_File: relaylog.000004
                Relay_Log_Pos: 340210
        Relay_Master_Log_File: binlog.000021
             Slave_IO_Running: Yes
            Slave_SQL_Running: No
......
          Exec_Master_Log_Pos: 340040

和直接使用mysqlbinlog解析binlog并执行的方法对比,用binlog实例进行时间点恢复需要创建一个临时空白实例,看起来更加复杂,但是有下面这些优点。

  1. 更加可靠。使用了mysql原生的复制代码来执行binlog,比使用mysqlbinlog解析binlog并执行更可靠。
  2. 效率更高。如果开启了多线程复制,应用binlog的效率会更高。
  3. 更容易处理异常。如果应用binlog的过程中遇到各种错误,可以像处理一个普通的备库遇到的问题那样来处理。

时间点恢复注意事项

使用备库产生的binlog恢复数据时,需要注意备库是否有延迟。如果备库本身存在延迟,那么备库上生成的binlog的时间戳和真实的业务时间可能存在偏差,依据这个时间来进行恢复,就可能达不到业务上的恢复目标。所以进行时间点恢复时,优先选择使用主库的binlog来进行恢复。当然在实际业务场景中,主备库之间可能会存在角色切换,需要想办法确定待恢复的时间点上,哪一个实例是主库。

我们也需要监控好主备库的复制状态,如果存在复制中断、复制延迟、甚至是主备库数据不一致的情况,需要及时处理。不然,在对备库进行的备份可能实际上是无效的。

前面讲的时间点恢复方案,优点是适应性广,理论上能恢复所有类型的数据误操作。但是如果数据库特别大,恢复的时间可能会比较久。恢复数据消耗的时间越长,业务受到的损失就可能越大。在一些特定的场景下,还可以采用其他一些恢复方法,提升恢复的速度,作为整个数据恢复方案的补充。

基于Binlog回滚的数据恢复

如果使用了ROW格式的Binlog,并且参数binlog_row_image为FULL,那么在执行DML语句时,会在Binlog中记录修改的记录的所有字段。如果是执行UPDATE或DELETE语句导致的数据问题,你可以在Binlog中找到修改前的数据,进行数据恢复。当然,如果是因为执行了tuncate table或drop table导致的数据丢失,binlog中没有记录具体的数据,就不能使用这种方式来恢复了。

怎么解析Binlog中的数据呢?有一些开源的工具,比如binlog2sql。其实你也可以使用mysqlbinlog来解析。

对于ROW格式的Binlog,mysqlbinlog加上-v选项,可以输出文本格式的数据。比如我在数据库上执行了这几个SQL。

update salaries set salaries = 999999 limit 100;
delete from employees limit 30;

使用mysqlbinlog -v,可以解析出这样的数据。

### UPDATE `employees`.`salaries`
### WHERE
###   @1=10001
###   @2=60117
###   @3='1986:06:26'
###   @4='1987:06:26'
### SET
###   @1=10001
###   @2=999999
###   @3='1986:06:26'
###   @4='1987:06:26'
### UPDATE `employees`.`salaries`
### WHERE
###   @1=10001
###   @2=62102
###   @3='1987:06:26'
###   @4='1988:06:25'
### SET
###   @1=10001
###   @2=999999
###   @3='1987:06:26'
###   @4='1988:06:25'

### DELETE FROM `employees`.`employees`
### WHERE
###   @1=20
###   @2='2013:10:10'
###   @3='AAAA'
###   @4='BBBB'
###   @5=1
###   @6='2024:08:03'
### DELETE FROM `employees`.`employees`
### WHERE
###   @1=10001
###   @2='1953:09:02'
###   @3='Georgi'
###   @4='Facello'
###   @5=1
###   @6='2024:08:03'

如果是Update更新错了,可以把Update的Binlog中,Where部分的字段提取出来,这些是更新前的数据。如果是Delete错了,把Where部分的字段提取出来,也可以恢复数据。

这里的格式比较简单,你可以写一些脚本来提取这些数据。这里我提供一段python代码作为参考。

import re
import sys

"""
mysqlbinlog --no-defaults -v binlog_file | python parse_binlog.py
or
mysqlbinlog --no-defaults -v binlog_file > decoded_binlog
python parse_binlog.py decoded_binlog
"""

if len(sys.argv) == 1:
    file = sys.stdin
else:
    file = open(sys.argv[1])

STATE_UPDATE = 10
STATE_DELETE = 20
STATE_INIT = 0

m_update = re.compile('### UPDATE `(.*)`[.]`(.*)`')
m_delete = re.compile('### DELETE FROM `(.*)`[.]`(.*)`')
m_set = re.compile('###   @[0-9]+=(.*)$')

def get_lines(file):
    while True:
        line = file.readline()
        if line:
            yield line
        else:
            return

def parse_binlog():
    state = STATE_INIT
    db = ''
    table = ''
    columns = []
    for line in get_lines(file):
        line = line.strip()
        if state == STATE_INIT:
            m = m_update.match(line)
            if m:
                state = STATE_UPDATE
                db, table = m.groups()
                columns = []
                continue
            m = m_delete.match(line)
            if m:
                state = STATE_DELETE
                db, table = m.groups()
                columns = []
                continue
        elif state == STATE_UPDATE:
            if line == '### WHERE':
                continue
            elif line == '### SET':
                state = STATE_INIT
                print_sql(db, table, columns, 'update')
                columns = []
            else:
                m = m_set.match(line)
                if m:
                    val = m.groups()[0]
                    columns.append(val)
        elif state == STATE_DELETE:
            if line == '### WHERE':
                continue
            else:
                m = m_set.match(line)
                if m:
                    val = m.groups()[0]
                    columns.append(val)
                else:
                    print_sql(db, table, columns, 'delete')
                    columns = []
                    m = m_delete.match(line)
                    if m:
                        state = STATE_DELETE
                        db, table = m.groups()
                    else:
                        state = STATE_INIT

def print_sql(db, table, columns, dml_type):
    print "-- dml_type: {}, db: {}, table: {}, columns: {}".format(dml_type, db, table, len(columns))
    print "replace into {}.{} values({})".format(db, table, ", ".join(columns))

if __name__ == '__main__':
    parse_binlog()

这样,你就可以找回误更新或误删除的数据了。

# mysqlbinlog -v mysql-binlog.000014 | python parse_binlog.py

-- dml_type: update, db: employees, table: salaries, columns: 4
replace into employees.salaries values(10001, 60117, '1986:06:26', '1987:06:26')
-- dml_type: update, db: employees, table: salaries, columns: 4
replace into employees.salaries values(10001, 62102, '1987:06:26', '1988:06:25')
...

-- dml_type: delete, db: employees, table: employees, columns: 6
replace into employees.employees values(10028, '1963:11:26', 'Domenick', 'Tempesti', 1, '1991:10:22')
-- dml_type: delete, db: employees, table: employees, columns: 6
replace into employees.employees values(10029, '1956:12:13', 'Otmar', 'Herbst', 1, '1985:11:20')

这里的方法虽然看起来比较简陋,但是在一些特定的场景下,可以帮你快速恢复数据。当然,如果在正式环境使用这类工具,我的建议是先把从Binlog中找回的数据写到一个临时表里,对数据进行验证后,再更新到正式的业务表里。

Xtrabackup单表恢复

如果表被DROP或Truncate了,你还可以考虑使用xtrabackup的单表恢复功能。当然,能使用单表恢复的前提,是使用了独立表空间(innodb_per_table=1)。

接下来我给你演示一下单表恢复的操作步骤。

1. Prepare备份文件

prepare时带上参数–export,xtrabackup会生成import tablespace需要的文件。

# xtrabackup --prepare --export --target-dir=. > export.log 2>&1

查看日志,确认prepare成功。

tail export.log

查看目标数据库下的文件,这里我们希望恢复demo.last_gtid表。

# ls -l demo/last_gtid.*
-rw-r--r-- 1 root root     715 6月  27 11:03 demo/last_gtid.cfg
-rw-r----- 1 root root 9437184 6月  26 16:08 demo/last_gtid.ibd

2. 目标数据库上创建希望恢复的表

表结构要和备份时的表结构一致。

mysql> show create table last_gtid\G
*************************** 1. row ***************************
       Table: last_gtid
Create Table: CREATE TABLE `last_gtid` (
  `id` int NOT NULL AUTO_INCREMENT,
  `gtid` varchar(1000) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=26733 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

我们先将表drop掉,模拟数据丢失的场景。

mysql> drop table last_gtid;
Query OK, 0 rows affected (0.03 sec)

创建结构,并执行discard tablespace命令。

mysql>  CREATE TABLE `last_gtid` (
       `id` int NOT NULL AUTO_INCREMENT,
       `gtid` varchar(1000) DEFAULT NULL,
       PRIMARY KEY (`id`)
     ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.06 sec)

mysql> alter table last_gtid discard tablespace;
Query OK, 0 rows affected (0.01 sec)

3. 将备份的文件copy到目标实例的数据库目录下

我们将表的cfg和ibd文件copy到目标实例数据库对应目录下,并修改文件owner。如果文件owner没有正确设置,下一步import表空间时可能会出错。

# cp last_gtid.* /data/full_restore/data/demo/

# ls -l /data/full_restore/data/demo/
-rw-r--r-- 1 root  root      715 6月  27 11:06 last_gtid.cfg
-rw-r----- 1 root  root  9437184 6月  27 11:06 last_gtid.ibd

# chown -R mysql:mysql /data/full_restore/data/demo/

4. import表空间

执行import tablespace命令恢复数据。确认数据已经恢复。

mysql> alter table demo.last_gtid import tablespace;
Query OK, 0 rows affected (0.29 sec)

mysql> select count(*) from demo.last_gtid;
+----------+
| count(*) |
+----------+
|    10813 |
+----------+
1 row in set (0.00 sec)

如果不知道表的表结构,可以使用ibd2sdi工具,从ibd文件中提取表结构信息。

# ibd2sdi last_gtid.ibd | more
["ibd2sdi"
,
{
    "type": 1,
    "id": 371,
    "object":
        {
    "mysqld_version_id": 80032,
    "dd_version": 80023,
    "sdi_version": 80019,
    "dd_object_type": "Table",
    "dd_object": {
        "name": "last_gtid",
        "mysql_version_id": 80032,
        "created": 20230621022444,
        "last_altered": 20230621022444,
        "hidden": 1,
        "options": "avg_row_length=0;encrypt_type=N;key_block_size=0;keys_disabled=0;pack_record=1;stats_a
uto_recalc=0;stats_sample_pages=0;",
        "columns": [
            {
                "name": "id",
                "type": 4,
                "is_nullable": false,
                "is_zerofill": false,
                "is_unsigned": false,
                "is_auto_increment": true,
                "is_virtual": false,
                ......

                "column_key": 2,
                "column_type_utf8": "int",
                "elements": [],
                "collation_id": 255,
                "is_explicit_collation": false
            },
            {
                "name": "gtid",
                "type": 16,
                "is_nullable": true,
                "is_zerofill": false,
                "is_unsigned": false,
                "is_auto_increment": false,
                "is_virtual": false,
                "hidden": 1,
                "ordinal_position": 2,
                "char_length": 4000,
                .....
                "column_key": 1,
                "column_type_utf8": "varchar(1000)",
                "elements": [],
                "collation_id": 255,
                "is_explicit_collation": false
            },

总结

在备份系统的设计中,要把Binlog备份也考虑进去,有了完整的Binlog,你可以将数据库恢复到任意一个时间点。在有些场景下,使用Binlog回滚的方案,能加快数据恢复的速度,减少业务的故障时长。

针对可能存在的数据丢失故障,你可以有计划地进行恢复演练,这不仅能验证备份和数据恢复方案的有效性,还能给恢复时长建立一个基线。这样,当需要紧急恢复数据时,你可以比较精确地估算出恢复需要多少时间。

思考题

如果数据库中有几个表被错误地Truncate了,如何在最短的时间内,将这几个表的数据恢复到执行truncate命令前的时间点?

期待你的思考,欢迎在留言区中与我交流。如果今天的课程让你有所收获,也欢迎转发给有需要的朋友。我们下节课再见。