0
2
3
0
专栏/.../

一文搞懂TiDB的闪回(FlashBack)能力

 数据源的TiDB学习之路  发表于  2024-02-20

闪回(FlashBack)是一种在数据库中进行异常数据恢复的高级技术,它可以将数据库还原到之前的某个时间点,从而消除误操作、错误数据或系统故障引起的问题。最早了解闪回功能是来自Oracle数据库中,闪回技术是Oracle强大数据库备份恢复机制的一部分,主要依赖UNDO撤销段中的撤销数据。随着分布式技术的发展,主流的分布式数据库也基本上都支持了闪回的能力,比如TiDB。不过由于数据库自身架构的不同,TiDB中闪回的技术原理可能与传统数据库或其他分布式数据库有所不同。

一. 闪回的前提之MVCC

要说TiDB闪回,首先得回顾一下TiDB的一个重要特性:MVCC。前面我们介绍闪回概念的时候提到它是将数据库还原到之前的某个时间点,那就意味着TiDB中要保留某个时间点的历史数据,否则就无法实现还原到这个时间点的目标。Oracle数据库把之前一段时间的数据保存在UNDO中,通过UNDO来保存数据的历史版本,TiDB没有UNDO这个东西,TiDB的多版本是通过在数据的Key后面添加版本号来实现的。

TiDB底层的数据存储TiKV本质上是一连串的Key Value键值对,在没有MVCC之前,可以把TiKV看成这样:

Key1 -> Value
Key2 -> Value
……
KeyN -> Value

有了MVCC之后,TiKV的Key排列是这样的:

Key1_Version3 -> Value
Key1_Version2 -> Value
Key1_Version1 -> Value
……
Key2_Version4 -> Value
Key2_Version3 -> Value
Key2_Version2 -> Value
Key2_Version1 -> Value
……
KeyN_Version2 -> Value
KeyN_Version1 -> Value
……

对于同一个Key的多个版本,版本号较大的放在前面,当按照Key+Version查询数据时,可以直接定位到第一个大于等于这个Key_Version的位置。

既然数据在TiDB中保存了多个版本,那么数据库就一定会有垃圾回收机制,否则就会造成数据膨胀严重且性能不断下降。关于垃圾回收模块,TiDB数据库有两个相关的参数:tikv_gc_safe_pointtidb_gc_life_time。tikv_gc_safe_point表示垃圾回收已经清理到的时间点,tidb_gc_life_time表示数据的历史版本保留时间(默认为10分钟)。以下输出示例表明当前环境的历史数据已经清理到20240220-10:54:17.961 +0800这个时间点,意味着这个时间点以前的数据被清理掉无法进行闪回了。

mysql> SELECT VARIABLE_NAME,VARIABLE_VALUE,COMMENT
    -> FROM mysql.tidb
    -> WHERE variable_name = "tikv_gc_safe_point";
+--------------------+-----------------------------+--------------------------------------------------------------+
| VARIABLE_NAME      | VARIABLE_VALUE              | COMMENT                                                      |
+--------------------+-----------------------------+--------------------------------------------------------------+
| tikv_gc_safe_point | 20240220-10:54:17.961 +0800 | All versions after safe point can be accessed. (DO NOT EDIT) |
+--------------------+-----------------------------+--------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> SHOW VARIABLES LIKE 'tidb_gc_life_time';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| tidb_gc_life_time | 10m0s |
+-------------------+-------+
1 row in set (0.00 sec)

二. 闪回恢复

TiDB中的闪回功能分为两类:一种是闪回恢复,即将数据恢复到某个时间点;另一种是闪回查询,即查询过去某个时间点的数据。关于闪回恢复,目前TiDB中主要支持以下几种方式:

  • 闪回表(FLASHBACK TABLE)

此功能在TiDB v4.0版本中引入。在GC life time时间内,如果一张表被drop或truncate掉后,可以使用这个功能来恢复到drop或truncate之前的状态。下面是一个简单的示例,

image.png

mysql> create table t20(a int primary key, b int);
Query OK, 0 rows affected (0.53 sec)

mysql> insert into t20 values(1,1),(2,2),(3,3);
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> select * from t20;
+---+------+
| a | b    |
+---+------+
| 1 |    1 |
| 2 |    2 |
| 3 |    3 |
+---+------+
3 rows in set (0.00 sec)

mysql> drop table t20;
Query OK, 0 rows affected (1.01 sec)

mysql> select * from t20;
ERROR 1146 (42S02): Table 'test.t20' doesn't exist
mysql> flashback table t20;
Query OK, 0 rows affected (0.53 sec)

mysql> select * from t20;
+---+------+
| a | b    |
+---+------+
| 1 |    1 |
| 2 |    2 |
| 3 |    3 |
+---+------+
3 rows in set (0.00 sec)

mysql> truncate table t20;
Query OK, 0 rows affected (1.02 sec)

mysql> select * from t20;
Empty set (0.01 sec)

mysql> flashback table t20;
ERROR 1050 (42S01): Table 't20' already exists
mysql> flashback table t20 to t21;
Query OK, 0 rows affected (1.03 sec)

mysql> select * from t21;
+---+------+
| a | b    |
+---+------+
| 1 |    1 |
| 2 |    2 |
| 3 |    3 |
+---+------+
3 rows in set (0.00 sec)

mysql> delete from t21;
Query OK, 3 rows affected (0.01 sec)

mysql> flashback table t21;
ERROR 1105 (HY000): Can't find dropped/truncated table 't21' in GC safe point 2024-02-20 11:44:17.961 +0800 CST
                                    

通过上述示例,我们发现两个需要注意的点:

  1. 如果表被truncate后进行闪回,由于原表仍然存在,需要闪回到一个不同的表名。
  2. Delete数据后无法使用闪回进行恢复的,针对delete后的恢复我们可以考虑用闪回查询的方式来恢复数据,后续会提及。
  • 闪回数据库(FLASHBACK DATABASE)

此功能在TiDB v6.4.0版本中引入。除了可以针对单张表进行闪回恢复,也可以针对一个DATABASE进行闪回恢复,它们的前提条件都是一样的,即必须要在GC life time时间内。以下是一个闪回数据库的示例:先创建一个数据库并创建两张测试表插入数据,之后删除数据库,最后使用FLASHBACK进行闪回。注意,闪回数据库也可以使用[TO newDBNname]指定恢复到一个不同的数据库名称。

mysql> create database tflashdb;
Query OK, 0 rows affected (1.02 sec)

mysql> use tflashdb;
Database changed
mysql> create table t1(a int, b int);
Query OK, 0 rows affected (0.53 sec)

mysql> insert into t1 values(1,1),(2,2),(3,3);
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> create table t2(a int, b int);
Query OK, 0 rows affected (0.52 sec)

mysql> insert into t2 values(1,1),(2,2),(3,3),(4,4);
Query OK, 4 rows affected (0.01 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> select * from t1;
+------+------+
| a    | b    |
+------+------+
|    1 |    1 |
|    2 |    2 |
|    3 |    3 |
+------+------+
3 rows in set (0.00 sec)

mysql> select * from t2;
+------+------+
| a    | b    |
+------+------+
|    1 |    1 |
|    2 |    2 |
|    3 |    3 |
|    4 |    4 |
+------+------+
4 rows in set (0.00 sec)

mysql> drop database tflashdb;
Query OK, 0 rows affected (1.02 sec)

mysql>
mysql> use tflashdb;
ERROR 1049 (42000): Unknown database 'tflashdb'
mysql> flashback database tflashdb;
Query OK, 0 rows affected (1.04 sec)

mysql> use tflashdb;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+--------------------+
| Tables_in_tflashdb |
+--------------------+
| t1                 |
| t2                 |
+--------------------+
2 rows in set (0.00 sec)

mysql> select * from t1;
+------+------+
| a    | b    |
+------+------+
|    1 |    1 |
|    2 |    2 |
|    3 |    3 |
+------+------+
3 rows in set (0.01 sec)

mysql> select * from t2;
+------+------+
| a    | b    |
+------+------+
|    1 |    1 |
|    2 |    2 |
|    3 |    3 |
|    4 |    4 |
+------+------+
4 rows in set (0.00 sec)
  • 闪回集群到时间点(FLASHBACK CLUSTER TO TIMESTAMP)

此功能在TiDB v6.4.0版本中引入。前面的FLASHBACK TABLE和FLASHBACK DATABASE目前均只能闪回到DROP/TRUNCATE操作前的状态,现有的TiDB版本(最新的7.5)还不支持将一个TABLE或一个DATABASE闪回到某个指定时间点。不过v6.4.0版本中支持闪回集群到某个时间点的功能,以下示例演示分别在两个数据库中删除表和数据,然后使用闪回集群到特定的时间点后数据可以正常恢复。

mysql> use za;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed

mysql> select count(*) from c_t1;
+-----------+
| count(*)  |
+-----------+
| 100000000 |
+-----------+
1 row in set (0.16 sec)

mysql> drop table c_t1;
Query OK, 0 rows affected (1.02 sec)

mysql> use test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed

mysql> select count(*) from tly;
+----------+
| count(*) |
+----------+
|        2 |
+----------+
1 row in set (0.01 sec)

mysql> delete from tly;
Query OK, 2 rows affected (0.00 sec)

mysql> select now();
+---------------------+
| now()               |
+---------------------+
| 2024-02-20 13:28:37 |
+---------------------+
1 row in set (0.00 sec)

mysql> flashback cluster to timestamp '2024-02-20 13:26:00';
Query OK, 0 rows affected (3.02 sec)

mysql> select count(*) from test.tly;
+----------+
| count(*) |
+----------+
|        2 |
+----------+
1 row in set (0.01 sec)

mysql> select count(*) from za.c_t1;
+-----------+
| count(*)  |
+-----------+
| 100000000 |
+-----------+
1 row in set (0.03 sec)

从这个例子中也可以看出,闪回到时间点的功能不仅可以解决DROP/TRUNCATE操作的问题,也可以解决误DELETE操作的问题。不过,如果只是一张表出现误删需要恢复到某个时间点而去恢复整个集群的话,显得动作有些大了。鉴于此,相信TiDB后续版本应该会增加表级别/库级别的闪回到时间点的功能

三. 闪回查询

闪回查询离不开TiDB的另外一个概念叫“Stale Read”。在文章 聊聊TiDB里面如何实现读写分离 (qq.com) 中,我们提到TiDB支持一种叫Follower Read的能力读取从副本从而降低主副本的负载。Stale Read则是一种读取历史数据版本的机制,通过Stale Read可以从指定时间点或时间范围内读取对应的历史数据。Stale Read因为是随机选择一个副本读取数据,且不使用强一致性的Follower读,可以避免跨中心的网络延迟,降低查询访问延迟。关于Stale Read的详细介绍,可以参考官网文档 Stale Read 功能的使用场景 | PingCAP 文档中心

Stale Read可以在语句级和会话级两种方式来使用,语句级别主要使用AS OF TIMESTAMP语法来实现。AS OF TIMESTAMP后可以接一个精确的时间戳,表示读取这个时间戳最新的数据;也可以接一个时间范围(使用TIDB_BOUNDED_STALENESS()函数),表示读取这个时间范围内尽可能新的数据。

闪回查询的语法也就是带AS OF TIMESTAMP的SELECT语句,以下示例演示AS OF TIMESTAMP分别指定时间戳和时间范围来进行闪回查询。与闪回恢复一样,要保证闪回查询成功,也必须满足查询的历史数据在GC life time范围内。

image.png

mysql> create table fquery(a int, b int);
Query OK, 0 rows affected (1.02 sec)

mysql> insert into fquery values(1,1);select now();
Query OK, 1 row affected (0.02 sec)

+---------------------+
| now()               |
+---------------------+
| 2024-02-20 14:27:46 |
+---------------------+
1 row in set (0.00 sec)

mysql> insert into fquery values(2,2);select now();
Query OK, 1 row affected (0.01 sec)

+---------------------+
| now()               |
+---------------------+
| 2024-02-20 14:27:57 |
+---------------------+
1 row in set (0.00 sec)

mysql> insert into fquery values(3,3);select now();
Query OK, 1 row affected (0.00 sec)

+---------------------+
| now()               |
+---------------------+
| 2024-02-20 14:28:07 |
+---------------------+
1 row in set (0.00 sec)

mysql> select * from fquery;
+------+------+
| a    | b    |
+------+------+
|    1 |    1 |
|    2 |    2 |
|    3 |    3 |
+------+------+
3 rows in set (0.00 sec)

mysql> select * from fquery as of timestamp '2024-02-20 14:27:50';
+------+------+
| a    | b    |
+------+------+
|    1 |    1 |
+------+------+
1 row in set (0.01 sec)

mysql> select * from fquery as of timestamp '2024-02-20 14:28:00';
+------+------+
| a    | b    |
+------+------+
|    1 |    1 |
|    2 |    2 |
+------+------+
2 rows in set (0.00 sec)

mysql> select * from fquery as of timestamp TIDB_BOUNDED_STALENESS('2024-02-20 14:27:50','2024-02-20 14:28:00');
+------+------+
| a    | b    |
+------+------+
|    1 |    1 |
|    2 |    2 |
+------+------+
2 rows in set (0.01 sec)

至此,我们了解了TiDB闪回的基本能力,包括闪回恢复和闪回查询的能力。闪回恢复支持将表恢复到DROP/TRUNCATE前的状态、将数据库恢复到DROP前的状态、将集群恢复到指定的时间点状态。闪回查询利用Stale Read中的AS OF TIMESTAMP语法来读取一个指定时间点或时间范围的最新状态。

0
2
3
0

版权声明:本文为 TiDB 社区用户原创文章,遵循 CC BY-NC-SA 4.0 版权协议,转载请附上原文出处链接和本声明。

评论
暂无评论