TiDB恢复被误删除的数据或表
Tags: drop恢复TiDBtruncate恢复误操作恢复闪回
概述
FLASHBACK TABLE
参考:https://docs.pingcap.com/zh/tidb/stable/sql-statement-flashback-table/
在 TiDB 4.0 中,引入了 FLASHBACK TABLE
语法,其功能是在 Garbage Collection (GC) life time 时间内,可以用 FLASHBACK TABLE
语句来恢复被 DROP
或 TRUNCATE
删除的表以及数据。
可以使用系统变量 tidb_gc_life_time
配置数据的历史版本的保留时间(默认值是 10m0s
)。可以使用以下 SQL 语句查询当前的 safePoint
,即 GC 已经清理到的时间点:
1 | SELECT * FROM mysql.tidb WHERE variable_name = 'tikv_gc_safe_point'; |
只要被 DROP
或 TRUNCATE
删除的表是在 tikv_gc_safe_point
时间之后,都能用 FLASHBACK TABLE
语法来恢复。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | MySQL [mysql]> select * from GLOBAL_VARIABLES where variable_name='tidb_gc_life_time'; +-------------------+----------------+ | VARIABLE_NAME | VARIABLE_VALUE | +-------------------+----------------+ | tidb_gc_life_time | 10m0s | +-------------------+----------------+ 1 row in set (0.05 sec) MySQL [mysql]> show variables like 'tidb_gc_life_time'; +-------------------+-------+ | Variable_name | Value | +-------------------+-------+ | tidb_gc_life_time | 10m0s | +-------------------+-------+ 1 row in set (0.07 sec) MySQL [(none)]> SELECT * FROM mysql.tidb; +--------------------------+------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------+ | VARIABLE_NAME | VARIABLE_VALUE | COMMENT | +--------------------------+------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------+ | bootstrapped | True | Bootstrap flag. Do not delete. | | tidb_server_version | 78 | Bootstrap version. Do not delete. | | system_tz | Asia/Shanghai | TiDB Global System Timezone. | | new_collation_enabled | False | If the new collations are enabled. Do not edit it. | | tikv_gc_leader_uuid | 5f8534338180001 | Current GC worker leader UUID. (DO NOT EDIT) | | tikv_gc_leader_desc | host:lhrtidb, pid:1711, start at 2022-01-01 16:45:35.655634965 +0800 CST m=+54.854095389 | Host name and pid of current GC leader. (DO NOT EDIT) | | tikv_gc_leader_lease | 20220103-09:44:38 +0800 | Current GC worker leader lease. (DO NOT EDIT) | | tikv_gc_enable | true | Current GC enable status | | tikv_gc_run_interval | 10m0s | GC run interval, at least 10m, in Go format. | | tikv_gc_life_time | 10m0s | All versions within life time will not be collected by GC, at least 10m, in Go format. | | tikv_gc_last_run_time | 20220103-09:35:38 +0800 | The time when last GC starts. (DO NOT EDIT) | | tikv_gc_safe_point | 20220103-09:25:38 +0800 | All versions after safe point can be accessed. (DO NOT EDIT) | | tikv_gc_auto_concurrency | true | Let TiDB pick the concurrency automatically. If set false, tikv_gc_concurrency will be used | | tikv_gc_scan_lock_mode | legacy | Mode of scanning locks, "physical" or "legacy" | | tikv_gc_mode | distributed | Mode of GC, "central" or "distributed" | +--------------------------+------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------+ 15 rows in set (0.05 sec) |
注意事项
如果删除了一张表并过了 GC lifetime,就不能再用 FLASHBACK TABLE
语句来恢复被删除的数据了,否则会返回错误,错误类似于 Can't find dropped/truncated table 't' in GC safe point 2020-03-16 16:34:52 +0800 CST
。
在开启 TiDB Binlog 时使用 FLASHBACK TABLE
需要注意以下情况:
- 下游从集群也支持
FLASHBACK TABLE
- 从集群的 GC lifetime 一定要长于主集群的 GC lifetime。上下游同步存在的延迟可能也会造成下游恢复数据失败。
如果 Binlog 同步出错,则需要在 Binlog 过滤掉该表,同时手动全量重新导入该表的数据。
工作原理
TiDB 在删除表时,实际上只删除了表的元信息,并将需要删除的表数据(行数据和索引数据)写一条数据到 mysql.gc_delete_range
表。TiDB 后台的 GC Worker 会定期从 mysql.gc_delete_range
表中取出超过 GC lifetime 相关范围的 key 进行删除。
所以,FLASHBACK TABLE
只需要在 GC Worker 还没删除表数据前,恢复表的元信息并删除 mysql.gc_delete_range
表中相应的行记录即可。恢复表的元信息可以用 TiDB 的快照读实现。具体的快照读内容可以参考读取历史数据文档。下面是 FLASHBACK TABLE t TO t1
的工作流程:
- 从 DDL History job 中查找
drop table
或者truncate table
类型的操作,且操作的表名是t
的第一个 DDL job,若没找到,则返回错误。 - 检查 DDL job 的开始时间,是否在
tikv_gc_safe_point
之前。如果是tikv_gc_safe_point
之前,说明被DROP
或TRUNCATE
删除的表已经被 GC 清理掉,返回错误。 - 用 DDL job 的开始时间作为 snapshot 读取历史数据,读取表的元信息。
- 删除
mysql.gc_delete_range
中和表t
相关的 GC 任务。 - 将表的元信息中的
name
修改成t1
,并用该元信息新建一个表。注意:这里只是修改了表名,但是 table ID 不变,依旧是之前被删除的表t
的 table ID。
可以发现,从表 t
被删除,到表 t
被 FLASHBACK
恢复到 t1
,一直都是对表的元信息进行操作,而表的用户数据一直未被修改过。被恢复的表 t1
和之前被删除的表 t
的 table ID 相同,所以表 t1
才能读取表t
的用户数据。
注意:
不能用
FLASHBACK
多次恢复同一个被删除的表,因为FLASHBACK
所恢复表的 table ID 还是被删除表的 table ID,而 TiDB 要求所有还存在的表 table ID 必须全局唯一。
FLASHBACK TABLE
是通过快照读获取表的元信息后,再走一次类似于 CREATE TABLE
的建表流程,所以 FLASHBACK TABLE
实际上也是一种 DDL 操作。
RECOVER TABLE
参考:https://docs.pingcap.com/zh/tidb/stable/sql-statement-recover-table/#recover-table
RECOVER TABLE
的功能是恢复被删除的表及其数据。在 DROP TABLE
后,在 GC life time 时间内,可以用 RECOVER TABLE
语句恢复被删除的表以及其数据。
语法:
1 2 | RECOVER TABLE table_name RECOVER TABLE BY JOB ddl_job_id |
注意事项
如果删除表后并过了 GC lifetime,就不能再用 RECOVER TABLE
来恢复被删除的表了,执行 RECOVER TABLE
语句会返回类似错误:snapshot is older than GC safe point 2019-07-10 13:45:57 +0800 CST
。
对于 3.0.0 及之后的 TiDB 版本,不推荐在使用 TiDB Binlog 的情况下使用 RECOVER TABLE
功能。
TiDB Binlog 在 3.0.1 支持 RECOVER TABLE
后,可在下面的情况下使用 RECOVER TABLE
:
- 3.0.1+ 版本的 TiDB Binlog
- 主从集群都使用 TiDB 3.0
- 从集群 GC lifetime 一定要长于主集群(不过由于上下游同步的延迟,可能也会造成下游 recover 失败)
TiDB Binlog 同步错误处理
当使用 TiDB Binlog 同步工具时,上游 TiDB 使用 RECOVER TABLE
后,TiDB Binlog 可能会因为下面几个原因造成同步中断:
- 下游数据库不支持
RECOVER TABLE
语句。类似错误:check the manual that corresponds to your MySQL server version for the right syntax to use near 'RECOVER TABLE table_name'
。 - 上下游数据库的 GC lifetime 不一样。类似错误:
snapshot is older than GC safe point 2019-07-10 13:45:57 +0800 CST
。 - 上下游数据库的同步延迟。类似错误:
snapshot is older than GC safe point 2019-07-10 13:45:57 +0800 CST
。
只能通过重新全量导入被删除的表来恢复 TiDB Binlog 的数据同步。
示例
根据表名恢复被删除的表。
1DROP TABLE t;1RECOVER TABLE t;根据表名恢复被删除的表需满足以下条件:
- 最近 DDL JOB 历史中找到的第一个
DROP TABLE
操作,且 DROP TABLE
所删除的表的名称与RECOVER TABLE
语句指定表名相同
- 最近 DDL JOB 历史中找到的第一个
根据删除表时的 DDL JOB ID 恢复被删除的表。
如果第一次删除表 t 后,又新建了一个表 t,然后又把新建的表 t 删除了,此时如果想恢复最开始删除的表 t, 就需要用到指定 DDL JOB ID 的语法了。
1DROP TABLE t;1ADMIN SHOW DDL JOBS 1;上面这个语句用来查找删除表 t 时的 DDL JOB ID,这里是 53:
12345+--------+---------+------------+------------+--------------+-----------+----------+-----------+-----------------------------------+--------+| JOB_ID | DB_NAME | TABLE_NAME | JOB_TYPE | SCHEMA_STATE | SCHEMA_ID | TABLE_ID | ROW_COUNT | START_TIME | STATE |+--------+---------+------------+------------+--------------+-----------+----------+-----------+-----------------------------------+--------+| 53 | test | | drop table | none | 1 | 41 | 0 | 2019-07-10 13:23:18.277 +0800 CST | synced |+--------+---------+------------+------------+--------------+-----------+----------+-----------+-----------------------------------+--------+1RECOVER TABLE BY JOB 53;根据删除表时的 DDL JOB ID 恢复被删除的表,会直接用 DDL JOB ID 找到被删除表进行恢复。如果指定的 DDL JOB ID 的 DDL JOB 不是
DROP TABLE
类型,会报错。
原理
TiDB 在删除表时,实际上只删除了表的元信息,并将需要删除的表数据(行数据和索引数据)写一条数据到 mysql.gc_delete_range
表。TiDB 后台的 GC Worker 会定期从 mysql.gc_delete_range
表中取出超过 GC lifetime 相关范围的 key 进行删除。
所以,RECOVER TABLE 只需要在 GC Worker 还没删除表数据前,恢复表的元信息并删除 mysql.gc_delete_range
表中相应的行记录就可以了。恢复表的元信息可以用 TiDB 的快照读实现。具体的快照读内容可以参考读取历史数据文档。
TiDB 中表的恢复是通过快照读获取表的元信息后,再走一次类似于 CREATE TABLE
的建表流程,所以 RECOVER TABLE
实际上也是一种 DDL。
示例
truncate示例
示例一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | C:\Users\lhrxxt>mysql -uroot -p -h192.168.66.35 -P44000 Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 15 Server version: 5.7.25-TiDB-v5.3.0 TiDB Server (Apache License 2.0) Community Edition, MySQL 5.7 compatible Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MySQL [(none)]> MySQL [(none)]> MySQL [(none)]> select tidb_version(); +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | tidb_version() | +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Release Version: v5.3.0 Edition: Community Git Commit Hash: 4a1b2e9fe5b5afb1068c56de47adb07098d768d6 Git Branch: heads/refs/tags/v5.3.0 UTC Build Time: 2021-11-24 13:32:39 GoVersion: go1.16.4 Race Enabled: false TiKV Min Version: v3.0.0-60965b006877ca7234adaced7890d7b029ed1306 Check Table Before Drop: false | +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.05 sec) MySQL [test]> select count(*) from noncluster_order; +----------+ | count(*) | +----------+ | 10 | +----------+ 1 row in set (0.05 sec) MySQL [test]> truncate table noncluster_order; Query OK, 0 rows affected (1.35 sec) MySQL [test]> select count(*) from noncluster_order; +----------+ | count(*) | +----------+ | 0 | +----------+ 1 row in set (0.05 sec) MySQL [test]> FLASHBACK TABLE noncluster_order to noncluster_order_old; Query OK, 0 rows affected (4.67 sec) MySQL [test]> select count(*) from noncluster_order; +----------+ | count(*) | +----------+ | 0 | +----------+ 1 row in set (0.06 sec) MySQL [test]> select count(*) from noncluster_order_old; +----------+ | count(*) | +----------+ | 10 | +----------+ 1 row in set (0.06 sec) MySQL [test]> drop table noncluster_order_old; Query OK, 0 rows affected (1.16 sec) MySQL [test]> select count(*) from noncluster_order_old; ERROR 1146 (42S02): Table 'test.noncluster_order_old' does not exist MySQL [test]> FLASHBACK TABLE noncluster_order_old; Query OK, 0 rows affected (2.23 sec) MySQL [test]> select count(*) from noncluster_order_old; +----------+ | count(*) | +----------+ | 10 | +----------+ 1 row in set (0.06 sec) MySQL [test]> drop table noncluster_order_old; Query OK, 0 rows affected (1.23 sec) MySQL [test]> select * from mysql.gc_delete_range; +--------+------------+--------------------+--------------------+--------------------+ | job_id | element_id | start_key | end_key | ts | +--------+------------+--------------------+--------------------+--------------------+ | 64 | 57 | 748000000000000039 | 74800000000000003a | 430224488837414914 | +--------+------------+--------------------+--------------------+--------------------+ 1 row in set (0.05 sec) |
示例二:
通过设置 tidb_snapshot 参数来读取历史数据,并用到 dumpling --snapshot 和FLASHBACK TABLE 恢复被多次 TRUNCATE 的数据。
- 初始化环境:
1 2 3 4 5 6 | MySQL [test]> create table trun_tab (c int); Query OK, 0 rows affected (0.71 sec) MySQL [test]> insert into trun_tab values (1), (2), (3); Query OK, 3 rows affected (0.29 sec) Records: 3 Duplicates: 0 Warnings: 0 |
- 模拟误 Truncate 表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | -- 重复执行了 2 次,并且 Truncate 后的表新 insert 了新的记录 MySQL [test]> truncate table trun_tab; Query OK, 0 rows affected (0.71 sec) MySQL [test]> insert into trun_tab values (4),(5),(6); Query OK, 3 rows affected (0.17 sec) Records: 3 Duplicates: 0 Warnings: 0 MySQL [test]> select * from trun_tab; +------+ | c | +------+ | 4 | | 5 | | 6 | +------+ 3 rows in set (0.05 sec) MySQL [test]> truncate table trun_tab; Query OK, 0 rows affected (0.73 sec) MySQL [test]> insert into trun_tab values (7),(8),(9),(10); Query OK, 4 rows affected (0.16 sec) Records: 4 Duplicates: 0 Warnings: 0 MySQL [test]> select * from trun_tab; +------+ | c | +------+ | 7 | | 8 | | 9 | | 10 | +------+ 4 rows in set (0.06 sec) |
数据恢复目标: 分别将第一次以及第二次 Truncate 前的数据找到,并恢复到原表 trun_tab 中。
确认是否满足 GC 要求: admin show ddl jobs 查看两次 Truncate 操作发生的时间
- 第一次:2022-01-04 11:18:19
- 第二次:2022-01-04 11:18:37
1 2 3 4 5 6 7 8 9 | MySQL [test]> admin show ddl jobs where table_name='trun_tab'; +--------+---------+------------+----------------+--------------+-----------+----------+-----------+---------------------+---------------------+--------+ | JOB_ID | DB_NAME | TABLE_NAME | JOB_TYPE | SCHEMA_STATE | SCHEMA_ID | TABLE_ID | ROW_COUNT | START_TIME | END_TIME | STATE | +--------+---------+------------+----------------+--------------+-----------+----------+-----------+---------------------+---------------------+--------+ | 86 | test | trun_tab | truncate table | public | 1 | 83 | 0 | 2022-01-04 11:18:37 | 2022-01-04 11:18:37 | synced | | 84 | test | trun_tab | truncate table | public | 1 | 81 | 0 | 2022-01-04 11:18:19 | 2022-01-04 11:18:19 | synced | | 82 | test | trun_tab | create table | public | 1 | 81 | 0 | 2022-01-04 11:17:14 | 2022-01-04 11:17:15 | synced | +--------+---------+------------+----------------+--------------+-----------+----------+-----------+---------------------+---------------------+--------+ 3 rows in set (0.06 sec) |
查看当前 GC 保留的 safe point,此处为 20220104-11:06:38 +0800,并且满足恢复要求:
1 2 3 4 5 6 7 | MySQL [test]> SELECT * FROM mysql.tidb WHERE variable_name = 'tikv_gc_safe_point'; +--------------------+-------------------------+--------------------------------------------------------------+ | VARIABLE_NAME | VARIABLE_VALUE | COMMENT | +--------------------+-------------------------+--------------------------------------------------------------+ | tikv_gc_safe_point | 20220104-11:06:38 +0800 | All versions after safe point can be accessed. (DO NOT EDIT) | +--------------------+-------------------------+--------------------------------------------------------------+ 1 row in set (0.05 sec) |
如果满足,那么调整 GC interval time,避免数据的 MVCC 历史版本被清理掉,影响数据恢复,此处为 720h:
1 | UPDATE mysql.tidb SET VARIABLE_VALUE = '720h' WHERE VARIABLE_NAME ='tikv_gc_life_time'; |
- 开始恢复数据: 设置 tidb_snapshot 为数据更改前的时间点,此处为 "2022-01-04 11:18:18",小于第一次Truncate 时间为 "2022-01-04 11:18:19"
1 2 3 4 5 6 7 8 9 10 11 12 | MySQL [test]> set session tidb_snapshot="2022-01-04 11:18:18"; Query OK, 0 rows affected (0.05 sec) MySQL [test]> select * from trun_tab; +------+ | c | +------+ | 1 | | 2 | | 3 | +------+ 3 rows in set (0.05 sec) |
恢复第一次 Truncate 的数据: 因为 Flashback 语句无法与 set tidb_snapshot 一起使用,即无法使用 Flashback 多次恢复同
一张表。
ERROR 1105 (HY000): can not execute write statement when 'tidb_snapshot' is seted.
使用 dumpling 备份目标 trun_tab 表数据,snapshot 的时间点为 "2022-01-04 11:18:18":
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | [root@lhrtidb /]# tiup dumpling \ > -u root \ > -P 4000 \ > --host 172.17.0.4 \ > --filetype sql \ > -o /tmp/test \ > -r 200000 \ > -F 256MiB \ > -T test.trun_tab \ > --snapshot "2022-01-04 11:18:18" Starting component `dumpling`: /root/.tiup/components/dumpling/v5.3.0/dumpling -u root -P 4000 --host 172.17.0.4 --filetype sql -o /tmp/test -r 200000 -F 256MiB -T test.trun_tab --snapshot 2022-01-04 11:18:18 Release version: v5.3.0 Git commit hash: 4a1b2e9fe5b5afb1068c56de47adb07098d768d6 Git branch: heads/refs/tags/v5.3.0 Build timestamp: 2021-11-24 01:31:47Z Go version: go version go1.16.4 linux/amd64 [2022/01/04 11:26:21.152 +08:00] [INFO] [versions.go:55] ["Welcome to dumpling"] ["Release Version"=v5.3.0] ["Git Commit Hash"=4a1b2e9fe5b5afb1068c56de47adb07098d768d6] ["Git Branch"=heads/refs/tags/v5.3.0] ["Build timestamp"="2021-11-24 01:31:47"] ["Go Version"="go version go1.16.4 linux/amd64"] [2022/01/04 11:26:21.155 +08:00] [INFO] [config.go:596] ["detect server type"] [type=TiDB] [2022/01/04 11:26:21.156 +08:00] [INFO] [config.go:615] ["detect server version"] [version=5.3.0] [2022/01/04 11:26:21.176 +08:00] [INFO] [client.go:352] ["[pd] create pd client with endpoints"] [pd-address="[172.17.0.4:2382,172.17.0.4:2379,172.17.0.4:2384]"] [2022/01/04 11:26:21.183 +08:00] [INFO] [base_client.go:331] ["[pd] update member urls"] [old-urls="[http://172.17.0.4:2382,http://172.17.0.4:2379,http://172.17.0.4:2384]"] [new-urls="[http://172.17.0.4:2379,http://172.17.0.4:2382,http://172.17.0.4:2384]"] [2022/01/04 11:26:21.183 +08:00] [INFO] [base_client.go:349] ["[pd] switch leader"] [new-leader=http://172.17.0.4:2379] [old-leader=] [2022/01/04 11:26:21.183 +08:00] [INFO] [base_client.go:104] ["[pd] init cluster id"] [cluster-id=7048155947995214464] [2022/01/04 11:26:21.183 +08:00] [INFO] [client.go:648] ["[pd] tso dispatcher created"] [dc-location=global] [2022/01/04 11:26:21.184 +08:00] [INFO] [dump.go:1162] ["generate dumpling gc safePoint id"] [id=dumpling_1641266781184692246] [2022/01/04 11:26:21.229 +08:00] [INFO] [dump.go:95] ["begin to run Dump"] [conf="{\"s3\":{\"endpoint\":\"\",\"region\":\"\",\"storage-class\":\"\",\"sse\":\"\",\"sse-kms-key-id\":\"\",\"acl\":\"\",\"access-key\":\"\",\"secret-access-key\":\"\",\"provider\":\"\",\"force-path-style\":true,\"use-accelerate-endpoint\":false},\"gcs\":{\"endpoint\":\"\",\"storage-class\":\"\",\"predefined-acl\":\"\",\"credentials-file\":\"\"},\"AllowCleartextPasswords\":false,\"SortByPk\":true,\"NoViews\":true,\"NoHeader\":false,\"NoSchemas\":false,\"NoData\":false,\"CompleteInsert\":false,\"TransactionalConsistency\":true,\"EscapeBackslash\":true,\"DumpEmptyDatabase\":true,\"PosAfterConnect\":false,\"CompressType\":0,\"Host\":\"172.17.0.4\",\"Port\":4000,\"Threads\":4,\"User\":\"root\",\"Security\":{\"CAPath\":\"\",\"CertPath\":\"\",\"KeyPath\":\"\"},\"LogLevel\":\"info\",\"LogFile\":\"\",\"LogFormat\":\"text\",\"OutputDirPath\":\"/tmp/test\",\"StatusAddr\":\":8281\",\"Snapshot\":\"2022-01-04 11:18:18\",\"Consistency\":\"snapshot\",\"CsvNullValue\":\"\\\\N\",\"SQL\":\"\",\"CsvSeparator\":\",\",\"CsvDelimiter\":\"\\\"\",\"Databases\":[],\"Where\":\"\",\"FileType\":\"sql\",\"ServerInfo\":{\"HasTiKV\":true,\"ServerType\":3,\"ServerVersion\":\"5.3.0\"},\"Rows\":200000,\"ReadTimeout\":900000000000,\"TiDBMemQuotaQuery\":0,\"FileSize\":268435456,\"StatementSize\":1000000,\"SessionParams\":{\"tidb_snapshot\":\"2022-01-04 11:18:18\"},\"Tables\":null}"] [2022/01/04 11:26:21.346 +08:00] [INFO] [writer.go:230] ["no data written in table chunk"] [database=test] [table=trun_tab] [chunkIdx=0] [2022/01/04 11:26:21.347 +08:00] [INFO] [collector.go:228] ["backup success summary"] [total-ranges=4] [ranges-succeed=4] [ranges-failed=0] [total-take=18.477116ms] [total-kv-size=74B] [average-speed=4.005kB/s] [total-rows=3] [2022/01/04 11:26:21.348 +08:00] [INFO] [main.go:80] ["dump data successfully, dumpling will exit now"] [root@lhrtidb /]# ll /tmp/test/^C [root@lhrtidb /]# cd /tmp/test [root@lhrtidb test]# ll total 16 -rw-r--r-- 1 root root 147 Jan 4 11:26 metadata -rw-r--r-- 1 root root 95 Jan 4 11:26 test-schema-create.sql -rw-r--r-- 1 root root 74 Jan 4 11:26 test.trun_tab.0000000010000.sql -rw-r--r-- 1 root root 143 Jan 4 11:26 test.trun_tab-schema.sql [root@lhrtidb test]# more test.trun_tab.0000000010000.sql /*!40101 SET NAMES binary*/; INSERT INTO `trun_tab` VALUES (1), (2), (3); [root@lhrtidb test]# more test-schema-create.sql /*!40101 SET NAMES binary*/; CREATE DATABASE `test` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; [root@lhrtidb test]# more test.trun_tab-schema.sql /*!40101 SET NAMES binary*/; CREATE TABLE `trun_tab` ( `c` int(11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; |
应用第一次 Truncate 前的数据到 trun_tab 表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | [root@lhrtidb test]# mysql -uroot -p -h172.17.0.4 -P4000 test Enter password: Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Welcome to the MariaDB monitor. Commands end with ; or \g. Your MySQL connection id is 80151 Server version: 5.7.25-TiDB-v5.3.0 TiDB Server (Apache License 2.0) Community Edition, MySQL 5.7 compatible Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MySQL [test]> source /tmp/test/test.trun_tab.0000000010000.sql Query OK, 0 rows affected (0.00 sec) Query OK, 3 rows affected (0.04 sec) Records: 3 Duplicates: 0 Warnings: 0 MySQL [test]> select * from trun_tab; +------+ | c | +------+ | 7 | | 8 | | 9 | | 10 | | 1 | | 2 | | 3 | +------+ 7 rows in set (0.00 sec) |
- 恢复第二次 Truncate 的数据: Flashback trun_tab 第二次 Truncate 前的数据到 trun_tab_02 中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | MySQL [test]> FLASHBACK TABLE trun_tab TO trun_tab_02; ERROR 1105 (HY000): can not execute write statement when 'tidb_snapshot' is set MySQL [test]> set session tidb_snapshot=""; Query OK, 0 rows affected (0.09 sec) MySQL [test]> FLASHBACK TABLE trun_tab TO trun_tab_02; Query OK, 0 rows affected (2.22 sec) MySQL [test]> select * from trun_tab_02; +------+ | c | +------+ | 4 | | 5 | | 6 | +------+ 3 rows in set (0.05 sec) MySQL [test]> insert trun_tab select * from trun_tab_02; Query OK, 3 rows affected (0.10 sec) Records: 3 Duplicates: 0 Warnings: 0 MySQL [test]> select * from trun_tab; +------+ | c | +------+ | 7 | | 8 | | 9 | | 10 | | 1 | | 2 | | 3 | | 4 | | 5 | | 6 | +------+ 10 rows in set (0.07 sec) |
至此两次数据恢复均完成,反馈执行结果给『业务端』,并确认二次恢复的结果。
drop示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | MySQL [test]> drop table trun_tab; Query OK, 0 rows affected (1.14 sec) MySQL [test]> admin show ddl jobs where table_name='trun_tab'; +--------+---------+------------+----------------+--------------+-----------+----------+-----------+---------------------+---------------------+--------+ | JOB_ID | DB_NAME | TABLE_NAME | JOB_TYPE | SCHEMA_STATE | SCHEMA_ID | TABLE_ID | ROW_COUNT | START_TIME | END_TIME | STATE | +--------+---------+------------+----------------+--------------+-----------+----------+-----------+---------------------+---------------------+--------+ | 88 | test | trun_tab | drop table | queueing | 1 | 85 | 0 | 2022-01-04 11:44:16 | 2022-01-04 11:44:16 | synced | | 86 | test | trun_tab | truncate table | public | 1 | 83 | 0 | 2022-01-04 11:18:37 | 2022-01-04 11:18:37 | synced | | 84 | test | trun_tab | truncate table | public | 1 | 81 | 0 | 2022-01-04 11:18:19 | 2022-01-04 11:18:19 | synced | | 82 | test | trun_tab | create table | public | 1 | 81 | 0 | 2022-01-04 11:17:14 | 2022-01-04 11:17:15 | synced | +--------+---------+------------+----------------+--------------+-----------+----------+-----------+---------------------+---------------------+--------+ 4 rows in set (0.06 sec) MySQL [test]> flashback table trun_tab ; Query OK, 0 rows affected (2.18 sec) MySQL [test]> select * from trun_tab; +------+ | c | +------+ | 7 | | 8 | | 9 | | 10 | | 1 | | 2 | | 3 | | 4 | | 5 | | 6 | +------+ 10 rows in set (0.05 sec) MySQL [test]> drop table trun_tab; Query OK, 0 rows affected (1.20 sec) MySQL [test]> recover table trun_tab ; Query OK, 0 rows affected (2.16 sec) MySQL [test]> select * from trun_tab; +------+ | c | +------+ | 7 | | 8 | | 9 | | 10 | | 1 | | 2 | | 3 | | 4 | | 5 | | 6 | +------+ 10 rows in set (0.05 sec) |
tidb_snapshot 读取历史数据并恢复
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | create table snap_tab (c int); insert into snap_tab values (1), (2), (3); MySQL [test]> select * from snap_tab; +------+ | c | +------+ | 1 | | 2 | | 3 | +------+ 3 rows in set (0.05 sec) MySQL [test]> select now(); +---------------------+ | now() | +---------------------+ | 2022-01-04 11:07:30 | +---------------------+ 1 row in set (0.05 sec) -- 误操作 update snap_tab set c=22 where c=2; MySQL [test]> select * from snap_tab; +------+ | c | +------+ | 1 | | 22 | | 3 | +------+ 3 rows in set (0.05 sec) -- 确认是否满足 GC 要求 ySQL [test]> SELECT * FROM mysql.tidb WHERE variable_name = 'tikv_gc_safe_point'; +--------------------+-------------------------+--------------------------------------------------------------+ | VARIABLE_NAME | VARIABLE_VALUE | COMMENT | +--------------------+-------------------------+--------------------------------------------------------------+ | tikv_gc_safe_point | 20220104-10:55:38 +0800 | All versions after safe point can be accessed. (DO NOT EDIT) | +--------------------+-------------------------+--------------------------------------------------------------+ 1 row in set (0.05 sec) -- 如果满足,那么调整 GC interval time,避免数据的 MVCC 历史版本被清理掉,影响数据恢复,此处为 720h: UPDATE mysql.tidb SET VARIABLE_VALUE = '720h' WHERE VARIABLE_NAME = 'tikv_gc_life_time'; MySQL [test]> SELECT * FROM mysql.tidb WHERE variable_name = 'tikv_gc_life_time'; +-------------------+----------------+----------------------------------------------------------------------------------------+ | VARIABLE_NAME | VARIABLE_VALUE | COMMENT | +-------------------+----------------+----------------------------------------------------------------------------------------+ | tikv_gc_life_time | 720h | All versions within life time will not be collected by GC, at least 10m, in Go format. | +-------------------+----------------+----------------------------------------------------------------------------------------+ 1 row in set (0.05 sec) |
开始数据恢复,设置 tidb_snapshot 为数据更改前的时间点,
1 2 3 4 5 6 7 8 9 10 11 12 | MySQL [test]> set @@tidb_snapshot="2022-01-04 11:07:30"; Query OK, 0 rows affected (0.05 sec) MySQL [test]> select * from snap_tab; +------+ | c | +------+ | 1 | | 2 | | 3 | +------+ 3 rows in set (0.05 sec) |
反馈查询结果给『业务端』,并且确认是否满足恢复目标
- 满足,那么继续下面的操作步骤
- 不满足,那么继续反复修改 tidb_snapshot,查看历史数据是否满足数据恢复需求
根据 tidb_snapshot 的查询结果生成反向 SQL。
清空当前 session tidb_snapshot 变量,然后进行恢复:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | MySQL [test]> SELECT @@tidb_snapshot; +---------------------+ | @@tidb_snapshot | +---------------------+ | 2022-01-04 11:07:30 | +---------------------+ 1 row in set (0.05 sec) MySQL [test]> set @@tidb_snapshot=""; Query OK, 0 rows affected (0.05 sec) MySQL [test]> select * from snap_tab; +------+ | c | +------+ | 1 | | 22 | | 3 | +------+ 3 rows in set (0.05 sec) MySQL [test]> update snap_tab set c=2 where c=22; Query OK, 1 row affected (0.10 sec) Rows matched: 1 Changed: 1 Warnings: 0 MySQL [test]> select * from snap_tab; +------+ | c | +------+ | 1 | | 2 | | 3 | +------+ 3 rows in set (0.05 sec) |
调整 GC interval time 为原值:
1 | UPDATE mysql.tidb SET VARIABLE_VALUE = '10m0s' WHERE VARIABLE_NAME = 'tikv_gc_life_time'; |