
# 闪回查询
#### 操作场景
在客户数据误删除或漏洞引发数据异常、业务审计需追溯历史数据等场景下，数据库闪回功能成为客户的核心需求。传统基于Binlog的闪回方案存在强依赖Binlog，低效等局限。针对上述痛点，TaurusDB支持基于Undo log的表级闪回查询功能，其核心优势在于：
- 内核级支持：直接利用UNDO表空间的历史数据镜像，无外部依赖。
- 精准恢复能力：支持表级数据回滚到指定时间点，精确到0.1秒级别。
- 支持单表闪回，无需保留闪回时间段内所有Undo log，只针对性保留闪回表的Undo log。
 
#### 版本约束
- TaurusDB实例内核版本大于等于2.0.69.250900，支持闪回查询。内核版本的查询方法请参见[如何查看云数据库 TaurusDB实例的版本号](https://support.huaweicloud.com/taurusdb_faq/taurusdb_faq_0141.html)。
- 若TaurusDB实例内核版本等于2.0.69.250900，Undo tablespace的truncate与闪回Undo log异步truncate存在互斥，即undo truncate特性与innodb_rds_backquery_undo_async_trunc不能同时打开。
 
#### 约束限制
- 仅支持InnoDB引擎表，不支持其他视图和其他引擎表。
- 闪回查询功能关闭再打开后，无法闪回到关闭前的时间。
- 闪回查询最低支持粒度为0.1秒级别。
- 闪回查询仅支持使用主键索引，或者InnoDB为无主键表自动生成的主键GEN_CLUST_INDEX。
- 不允许闪回时间窗口内发生DDL，尤其是**DROP TABLE** , **TRUNCATE TABLE** ，**ADD COLUMN** , **DROP COLUMN** ，**OPTIMIZE TABLE**等会改变Cluster Index（聚簇索引）的操作，仅允许闪回表在闪回时间窗口内发生DML。
- 不支持的语句
  - **Prepared statement**（预处理语句）
  
  - **Stored procedure**（存储过程）
  
  - 复杂语句
    - **CREATE TABLE AS ...**
    
    - **INSERT SELECT ...**
    
    - **REPLACE SELECT ...**
     
   
- 当"innodb_rds_backquery_enable"参数更改时，参数变更需要异步生效。如果在异步生效期间再次修改参数"innodb_rds_backquery_enable"，将报"The BACKQUERY state cannot be changed because the previous state modification is not yet complete" 错误。
- 闪回查询不支持迁移场景。本特性基于Undo log实现。在迁移等场景创建的新实例中，由于缺少原实例的Undo log，无法通过闪回特性查询新实例创建之前的历史数据。
- 在业务写入压力大时，Purge存在性能瓶颈。限制InnoDB History list长度可以缓解此瓶颈，具体做法如下：
  - 建议非IO型业务开启闪回。当History list过大可能触发InnoDB Buffer pool频繁页面换出时，InnoDB Purge性能将受较大影响。
  
  - 限制闪回时间窗口长度。在写业务压力大时，通过参数"innodb_rds_backquery_window"控制闪回时长，避免History list过长。
   
 
#### 原理介绍
1. Undo log的Purge流程：
   1. 第一轮Purge为原生InnoDB触发的Purge，使用的MVCC的m_views中最老的Readview，将彻底清理非闪回表的主键索引和二级索引，清理闪回表的二级索引，保留其聚簇索引。 触发的来源包括：更新数据字典统计信息、崩溃恢复或原子DDL的撤销流程中、关闭undo tablespace时、InnoDB master线程中常规检查、事务提交时、通过设置参数手动开启或关闭Purge时、关闭MySQL进程时等等。
      
   
   2. 第二轮Purge为闪回后台线程触发的Purge，使用闪回系统最老的Readview（某一时刻的活跃事务列表），将彻底清理闪回表的主键索引。
    
2. Undo log的Truncation（删除undo log）流程：
   1. 首先进行MVCC Truncation，删除不包含闪回表的Undo log。
   
   2. 其次进行闪回Truncation，删除包含闪回表的Undo log。
   
   
   MVCC Truncation和闪回Truncation没有先后关系，只负责在Readview范围内清理相应的Undo log。Undo log状态示意图如下：
   图1Undo log Truncation示意图   
   ![](https://support.huaweicloud.com/kerneldesc-taurusdb/figure/zh-cn_image_0000002555984379.png "点击放大") 
3. 闪回查询 闪回查询时选用指定时间点的Readview通过History list构建数据历史数据视图，返回查询结果。
   
 
#### 参数说明
在管理控制台的参数修改页面，通过设置[表1]，可以开启闪回查询及设置闪回Readview和闪回查询的时间。
 表1参数说明 
| **参数名称**                                     | 参数类型    | **参数说明**                                                                                                                                                                                                                                                                                                                                                                          |
|:---|:---|:---|
| innodb_rds_backquery_enable                  | Boolean | 闪回查询功能的总开关。 - true：开启闪回查询。  - false：关闭闪回查询。                                            |
| innodb_rds_backquery_readview_interval       | INT     | 闪回Readview生成的时间间隔。单位：0.1秒。 默认值：10。取值范围：1-10。 该值越小，闪回查询相对越准确，但是用于存储Readview的系统表存储的数据越多，空间占用越大。                                                                                                                                                                                      |
| innodb_rds_backquery_window                  | INT     | 闪回查询支持最长留存时间。单位：秒。 默认值：3600。取值范围：60-604800。 此时间越长，Undo log占用的存储空间也将越多。                                                                                                                                                                                                              |
| Innodb_rds_backquery_purge_mvcc_pct          | DOUBLE  | MVCC Purge占MVCC Purge和BACKQUERY Purge总次数的比例。单位：%。 默认值：50。取值范围：1-99。                                                                                                                                                                                                                                                              |
| Innodb_rds_backquery_undo_async_trunc        | Boolean | 开启闪回开关后，是否启用异步线程来删除Undo log。 - true：开启Undo log异步删除。  - false：关闭Undo log异步删除。   |
| Innodb_rds_backquery_purge_batches_per_cycle | INT     | 单轮次Purge最多处理的页面数量。 默认值：300。取值范围：1-5000。                                                                                                                                                                                                                                                                                             |
| Innodb_rds_backquery_purge_interval          | INT     | 闪回后台线程触发Purge的时间间隔。单位：毫秒。 默认值：5。取值范围1-50。                                                                                                                                                                                                                                                                                          |
   
#### 新增系统表
TaurusDB已默认新增用于存储闪回Readview的系统表如下，您无需对系统表执行操作，TaurusDB会默认对系统表进行数据插入及清理操作。
```
CREATE TABLE `__taurus_sys__`.`backquery` (
  `Snapshot_time` bigint unsigned NOT NULL,
  `Low_limit_id` bigint unsigned NOT NULL,
  `Up_limit_id` bigint unsigned NOT NULL,
  `Low_limit_no` bigint unsigned NOT NULL,
  `Trx_ids_count` bigint unsigned NOT NULL,
  `Trx_ids` mediumblob,
  PRIMARY KEY (`Snapshot_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin STATS_PERSISTENT=0 ROW_FORMAT=DYNAMIC COMMENT='Backquery system table.'
```
#### 使用指导
- 支持创建表时标记为闪回表或将已存在的非闪回表修改为闪回表
  - 创建表时标记为闪回表。 在创建InnoDB表时通过设置BACKQUERY选项，将表标记为闪回表，如果不指定BACKQUERY选项，表示创建的表是非闪回表。
    BACKQUERY选项的取值范围：
    - 0：表示非闪回表。
    
    - 1：表示闪回表。
    
    - default：表示非闪回表。
    
    
    ```
    CREATE TABLE my_table(
        id INT AUTO_INCREMENT PRIMARY KEY,
        data VARCHAR(255)
    ) ENGINE=InnoDB, BACKQUERY=1;
    ```
    
  
  - 支持将已存在的非闪回表标记为闪回表。
    ```
    CREATE TABLE my_table(
        id INT AUTO_INCREMENT PRIMARY KEY,
        data VARCHAR(255)
    ) ENGINE=InnoDB;
    ```
    ```
    ALTER TABLE my_table BACKQUERY=1;
    ```
    
   
- 支持SELECT ... FROM *table_name* AS OF TIMESTAMP *time* 语法
  通过time指定闪回查询的时间点，精确到0.1秒。
  - 如果时间没有填写完整，则自动将后续数值填写为0。
  
  - 当输入的时间不存在相应的Readview时，在"innodb_rds_backquery_window"参数设置的时间范围内自动找时间更早的readview。
  
  
  **示例：**
  查询employee表在2026-03-09 17:38:18的第5个0.1秒时的数据。
  ```
  INSERT INTO employee(id, name) VALUES(101, "John Doe");
  Query OK, 1 row affected (0.01 sec)
  ```
  ```
  SELECT NOW();
  +---------------------+
  | NOW()               |
  +---------------------+
  | 2026-03-09 17:38:18 |
  +---------------------+
  1 row in set (0.00 sec)
  ```
  ```
  UPDATE employee SET name = "Jane Doe" WHERE id = 101;
  Query OK, 1 row affected (0.00 sec)
  Rows matched: 1  Changed: 1  Warnings: 0
  ```
  ```
  SELECT * FROM employee AS OF TIMESTAMP '2026-03-09 17:38:18.5';
  +------+----------+
  | id   | name     |
  +------+----------+
  |  101 | John Doe |
  +------+----------+
  1 row in set (0.00 sec)
  ```
  
- 支持复杂语句
  - JOIN语句 下述SQL示例查询的是对两张表不同时间的闪回数据进行关联连接。闪回查询语法仅支持绑定单张表，属于单表独有属性。因此只有部门表department 会以历史时间状态被查询，员工表employee则仍按普通查询，保持当前最新状态不变。
    时间戳可以通过SQL变量传入，其中@a表示一个预先定义且落在闪回查询时间窗口范围内的任意时刻。
    ```
    SELECT employee.name, department.name AS department FROM employee 
          JOIN department AS OF TIMESTAMP @a ON employee.departmentID = department.id;
    +---------------+-------------+
    | name          | department  |
    +---------------+-------------+
    | Jane Doe      | HR          |
    | Bob Jones     | Engineering |
    | Charlie Davis | HR          |
    | Diana Evans   | Engineering |
    +---------------+-------------+
    4 rows in set (0.01 sec)
    ```
    
  
  - UNION语句 下述SQL示例查询通过UNION将employeeA 表的实时数据，以及employeeB表在@a时刻的闪回数据合并为统一结果集。
    ```
    SELECT * FROM employeeA UNION SELECT * FROM employeeB AS OF TIMESTAMP @a;
    +------+---------------+
    | id   | name          |
    +------+---------------+
    |  101 | Jane Doe      |
    |  104 | Diana Evans   |
    |  102 | Bob Jones     |
    |  103 | Charlie Davis |
    +------+---------------+
    4 rows in set (0.01 sec)
    ```
    
  
  - 子查询 下述SQL示例查询读取当前实时的员工列表，并对每一行记录通过子查询进行闪回查询，精准获取@a时刻对应的部门名称。
    ```
    SELECT e.name AS name,
        (SELECT d.name FROM department AS OF TIMESTAMP @a d WHERE d.id = e.departmentID) AS department FROM employee e;
    +---------------+-------------+
    | name          | department  |
    +---------------+-------------+
    | Jane Doe      | HR          |
    | Bob Jones     | Engineering |
    | Charlie Davis | HR          |
    | Diana Evans   | Engineering |
    +---------------+-------------+
    4 rows in set (0.00 sec)
    ```
    
  
  - 使用不同的时间表达式 以Join语句举例，下述SQL示例查询以两张表各自不同的闪回数据做关联连接。其中@a表示员工数据被删除前的某一时刻，@b表示新增Product 部门后的某一时刻。
    查询将@a时刻的历史员工数据与@b时刻的部门数据进行匹配关联，从而将两个不同时间点的数据整合为统一结果集。
    ```
    SELECT * FROM employee;
    +------+---------------+--------------+
    | id   | name          | departmentID |
    +------+---------------+--------------+
    |  101 | Jane Doe      |            1 |
    |  102 | Bob Jones     |            2 |
    |  103 | Charlie Davis |            1 |
    |  104 | Diana Evans   |            2 |
    +------+---------------+--------------+
    4 rows in set (0.00 sec)
    SELECT * FROM department;
    +------+-------------+
    | id   | name        |
    +------+-------------+
    |    2 | Engineering |
    +------+-------------+
    1 row in set (0.00 sec)
    SET @a = NOW();
    Query OK, 0 rows affected (0.00 sec)
    DELETE FROM employee;
    Query OK, 4 rows affected (0.01 sec)
    INSERT INTO department (id, name) VALUES (1, "Product");
    Query OK, 1 row affected (0.00 sec)
    SET @b = NOW();
    Query OK, 0 rows affected (0.00 sec)
    SELECT employee.name, department.name AS department FROM employee AS OF TIMESTAMP @a 
        JOIN department AS OF TIMESTAMP @b ON employee.departmentID = department.ID;
    +---------------+-------------+
    | name          | department  |
    +---------------+-------------+
    | Diana Evans   | Engineering |
    | Bob Jones     | Engineering |
    | Charlie Davis | Product     |
    | Jane Doe      | Product     |
    +---------------+-------------+
    4 rows in set (0.00 sec)
    ```
    
   
 
#### 常见问题
#### 如果闪回查询功能先禁用再重新开启，是否支持查询禁用前的数据？
不支持。禁用innodb_rds_backquery_enable参数后，undo表空间中保留的历史数据将全部失效。重新启用后，闪回查询仅能查询重新启用后新生成的数据，无法查询功能禁用期间的数据。
#### 多次修改闪回功能参数，报错"The BACKQUERY state cannot be changed..."如何处理？
由于上次的状态修改还未完成，因此无法修改BACKQUERY状态，请您耐心等待上次状态修改完成。
