对于 DBA 来说,删库跑路永远是被调侃的哏,近在眼前的某微商平台的大面积宕机事件给企业管理人员及数据库运维团队带来的启示仍在被不断讨论。当然为了应对大面积的恶意删库跑路行为,可能真的是防不胜防,不过这种情况也非常罕见。但是大家尤其是线上运维 DBA 在维护数据库的过程中,出现删错表/库的情况却是很容易出现的,下面来看一下 TiDB 提供的快速恢复误删表的功能。
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。
MySQL [test]> show tables;
+----------------+
| Tables_in_test |
+----------------+
| t1 |
| t3 |
+----------------+
**2 rows in set (0.00 sec)**
MySQL [test]> create table t2 like t1;
**Query OK, 0 rows affected (0.10 sec)**
MySQL [test]> insert into t2 select * from t1;
**Query OK, 524288 rows affected (17.10 sec)**
**Records: 524288 Duplicates: 0 Warnings: 0**
MySQL [test]> show tables;
+----------------+
| Tables_in_test |
+----------------+
| t1 |
| t2 |
| t3 |
+----------------+
**3 rows in set (0.00 sec)**
执行 DDL 删除 t2 表。
MySQL [test]> drop table t2;
**Query OK, 0 rows affected (0.24 sec)**
MySQL [test]> show tables;
+----------------+
| Tables_in_test |
+----------------+
| t1 |
| t3 |
+----------------+
**2 rows in set (0.00 sec)**
MySQL [test]> select * from t2 limit 1;
ERROR 1146 (42S02): Table 'test.t2' doesn't exist
这个时候执行 recover 操作
MySQL [test]> recover table t2;
**Query OK, 0 rows affected (1.17 sec)**
MySQL [test]> show tables;
+----------------+
| Tables_in_test |
+----------------+
| t1 |
| t2 |
| t3 |
+----------------+
**3 rows in set (0.00 sec)**
MySQL [test]> select count(1) from t2;
+----------+
| count(1) |
+----------+
| 524288 |
+----------+
**1 row in set (0.55 sec)**
可以看到table t2 已经回来了。当然根据前面的原理介绍,如果超过 gc 时间,这里就会返回下面错误
MySQL [test]> recover table t2;
ERROR 8055 (HY000): snapshot is older than GC safe point 2020-03-08 15:06:19 +0800 CST
另外还有一种用法,可以通过查询 ddl jobs 队列,指定 job id 的方式来进行恢复。
MySQL [test]> ADMIN SHOW DDL JOBS;
+--------+---------+------------+---------------+--------------+-----------+----------+-----------+-----------------------------------+-----------------------------------+--------+
| JOB_ID | DB_NAME | TABLE_NAME | JOB_TYPE | SCHEMA_STATE | SCHEMA_ID | TABLE_ID | ROW_COUNT | START_TIME | END_TIME | STATE |
+--------+---------+------------+---------------+--------------+-----------+----------+-----------+-----------------------------------+-----------------------------------+--------+
| 73 | test | t2 | drop table | none | 1 | 71 | 0 | 2020-03-08 15:38:47.076 +0800 CST | 2020-03-08 15:38:47.276 +0800 CST | synced |
| 72 | test | t2 | create table | public | 1 | 71 | 0 | 2020-03-08 15:38:37.626 +0800 CST | 2020-03-08 15:38:37.726 +0800 CST | synced |
| 70 | test | t2 | drop table | none | 1 | 66 | 0 | 2020-03-08 15:38:20.576 +0800 CST | 2020-03-08 15:38:20.776 +0800 CST | synced |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
从 DDL 记录可以看到,我们一共执行了 2 次 drop table t2 的操作,如果只执行 recover table t2 恢复的是最近一次删除的表。如果我希望恢复前面一次删除的动作,就需要用到下面的命令
MySQL [test]> recover table by job 70;
**Query OK, 0 rows affected (1.15 sec)**
recover table 的一些使用限制:
Flashback 的原理和 Recover table 比较类似,不过是 recover 的升级版,在覆盖 recover 的所有功能之外,还可以支持 truncate table 的操作,未来也会逐渐取代 recover 命令。下面是简单的使用示例,其他不再累述。
MySQL [test]> show tables;
+----------------+
| Tables_in_test |
+----------------+
| t1 |
| t2 |
| t3 |
+----------------+
**3 rows in set (0.00 sec)**
MySQL [test]> truncate table t2;
**Query OK, 0 rows affected (0.11 sec)**
MySQL [test]> flashback table t2 to t4;
**Query OK, 0 rows affected (1.16 sec)**
MySQL [test]> show tables;
+----------------+
| Tables_in_test |
+----------------+
| t1 |
| t2 |
| t3 |
| t4 |
+----------------+
**4 rows in set (0.00 sec)**
MySQL [test]> select count(1) from t2;
+----------+
| count(1) |
+----------+
| 0 |
+----------+
**1 row in set (0.01 sec)**
MySQL [test]> select count(1) from t4;
+----------+
| count(1) |
+----------+
| 524288 |
+----------+
**1 row in set (0.39 sec)**
需要注意的是,目前 flashback 命令还不支持指定 ddl job id 来恢复表。