TiDB 数据库紧急救援参考手册(数据恢复)
文档控制
| 项目名称 | |||
|---|---|---|---|
| 文档名称 | |||
| 1. 文档属性 | |||
| 文档编号 | |||
| 2. 修改记录 | |||
| 日期 | 修改人 | 修改记录 | 备注 |
| 3. 审阅记录 | |||
| 日期 | 审阅人 | 公司名称及职务 | 备注 |
版权声明
平凯星辰(北京)科技有限公司版权所有,保留一切权利。未经本公司许可,任何单位和个人不得擅自摘抄、复制本文档内容,并以任何形式传播。
目录
文档控制2
一、问题确认4
二、使用场景4
三、利用快照读恢复数据5
使用条件5
操作步骤5
四、利用Flashback/Recover 命令秒恢复误删表6
使用条件6
Recover命令7
Recover操作步骤(方法一) 7
Recover操作步骤(方法二) 8
Flashback 命令(适用版本>= v4.0.0) 9
Flashback操作步骤9
五、Dumpling 导出 TiDB 的历史数据11
1、使用条件11
2、dumpling导出11
3、导入 dumpling 备份数据12
六、多数副本丢失数据恢复12
问题背景12
2个副本丢失处理12
3个副本丢失处理17
一、问题确认
-
问题发生的时间,集群发生的问题( 比如误删数据,多副本丢失)
-
用户期望(如先恢复部分重要数据优先)
二、使用场景
-
用户在处理数据的过程中,执行了错误的更新、删除操作。这时在 TiDB 中如何快速恢复误操作前的数据,请参考 【利用 GC 快照读恢复数据】
-
如果直接执行了 drop table,或者 truncate table,将整张表瞬间删除,这时如何恢复,请参考【利用 Recover/Flashback 命令秒恢复误删
-
如果需要指定时间点导出历史数据到文件,请参考 【Dumpling 导出 TiDB 的历史数据快照】
-
TiKV 虽然已有高可用方案,采用 3 副本的方式冗余数据,但如果特殊意外丢失了 2 副本该如何恢复,请参考【多数副本丢失数据恢复指南】
| 数据恢复场景 | 错误的更新、删除操作**(update,delete)** | 误删表**(Drop table)** | 导出历史数据到文件后恢复 | 2副本丢失恢复 | 3副本丢失恢复集群状态 |
|---|---|---|---|---|---|
| 恢复条件 | 恢复 tikv_gc_safe_point 时间之后的数据 | 恢复 tikv_gc_safe_point 时间之后的数据 | 恢复 tikv_gc_safe_point 时间之后的数据 | 存在1副本 | 可以接受数据丢失 |
| 工具 | 设置 tidb_snapshot变量 | Flashback/Recover 命令 | dumpling | tikv-ctlunsafe-recover | tikv-ctlrecreate-region |
| 工具版本选择 | 与tidb 相同 | v4 ,v5 支持 FLASHBACK / RECOVER,v3 仅支持 RECOVER; | 选择dumpling最新版本 | 与tidb 相同 | 与tidb 相同 |
| 恢复影响范围 | 恢复的表 | 恢复的表 | 恢复的表 | 需要停止tikv服务和重启集群 | 需要停止tikv服务和重启集群 |
| 按重要性选择恢复数据 | 是 | 是 | 是 | 是 | 否 |
| 是否有损恢复 | 否 | 否 | 否 | 是 | 是 |
| 恢复耗时估计 | 小于1分钟 | 小于1分钟 | 和恢复数据量有关 | 分钟级别 | 分钟级别 |
三、利用快照读恢复数据
使用条件
只适用于恢复 tikv_gc_safe_point 时间之后的数据,可以使用以下 SQL 语句查询当前的 safePoint,即 GC 已经清理到的时间点:
| SELECT * FROM mysql.tidb WHERE variable_name = 'tikv_gc_safe_point'; |
|---|
操作步骤
1、调整 GC 保留时间,如将 GC 调整为保留最近一天以内的数据。
| update mysql.tidb set VARIABLE_VALUE="24h" where VARIABLE_NAME="tikv_gc_life_time"; |
|---|
2、设置tidb_snapshot变量为需要恢复的历史时间点,创建一个与待恢复的数据表同结构的临时表
| set @@tidb_snapshot="2021-10-08 16:45:26";create table t_20211008 like t; |
|---|
3、按照业务逻辑将需要的数据插入到临时表:
| insert into t_20211008 select * from t where c=2; |
|---|
4、按照业务逻辑将数据从临时表反更新或插入到原表
5、按照业务逻辑校验数据
6、将 GC 保留时长调整为恢复之前的设置
| update mysql.tidb set VARIABLE_VALUE="10m0s" where VARIABLE_NAME="tikv_gc_life_time" |
|---|
-
根据需要删除临时表
四、利用Flashback/Recover 命令秒恢复误删表
使用条件
只要被 DROP 或 TRUNCATE 删除的表是在 tikv_gc_safe_point 时间之后,都能用 FLASHBACK TABLE 语法来恢复。
如果删除了一张表并过了 GC lifetime,就不能再用 FLASHBACK TABLE 语句来恢复被删除的数据了,否则会返回错误,错误类似于 Can't find dropped/truncated table 't' in GC safe point 2020-03-16 16:34:52 +0800 CST。
可以使用系统变量 tidb_gc_life_time 配置数据的历史版本的保留时间(默认值是 10m0s)。可以使用以下 SQL 语句查询当前的 safePoint,即 GC 已经清理到的时间点:
SELECT * FROM mysql.tidb WHERE variable_name = 'tikv_gc_safe_point';
Recover命令
v3 仅支持 RECOVER;
Recover操作步骤(方法一)
1、模拟误删除操作
| MySQL [test]> drop table t2;MySQL [test]> show tables;+----------------+| Tables_in_test |+----------------+| t1 || t3 |+----------------+MySQL [test]> select * from t2 limit 1;ERROR 1146 (42S02): Table 'test.t2' doesn't exist | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2、执行 recover 操作
| MySQL [test]> recover table t2;MySQL [test]> show tables;+----------------+| Tables_in_test |+----------------+| t1 || t2 || t3 |+----------------+ | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
- 恢复完成,业务确认检查。
Recover操作步骤(方法二)
通过查询 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; |
|---|
Flashback 命令(适用版本>= v4.0.0)
可恢复 truncate 误操作。
Flashback操作步骤
1、恢复被 DROP 删除的表数据(例如:t)
| FLASHBACK TABLE t; |
|---|
2、恢复被 TRUNCATE 的表数据
由于被 TRUNCATE 的表还存在,所以需要重命名被恢复的表,否则会报错表 t 已存在。
| MySQL [test]> show tables;+----------------+| Tables_in_test |+----------------+| t1 || t2 || t3 |+----------------+MySQL [test]> truncate table t2;MySQL [test]> flashback table t2 to t4;MySQL [test]> show tables;+----------------+| Tables_in_test |+----------------+| t1 || t2 || t3 || t4 |+----------------+MySQL [test]> select count(1) from t2;+----------+| count(1) |+----------+| 0 |+----------+MySQL [test]> select count(1) from t4;--同上+----------+| count(1) |+----------+| 524288 |+----------+ | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
五、Dumpling 导出 TiDB 的历史数据
使用 Dumpling 导出 TiDB 的历史数据快照,Dumpling 在导出较大的数据集时有较好的性能。
dumpling可以把存储在 TiDB 中的数据导出为 SQL 或 CSV 格式,用于逻辑全量备份。
1、使用条件
只适用于恢复 tikv_gc_safe_point 时间之后的数据,可以使用以下 SQL 语句查询当前的 safePoint,即 GC 已经清理到的时间点:
| SELECT * FROM mysql.tidb WHERE variable_name = 'tikv_gc_safe_point'; |
|---|
2、dumpling导出
设置 --snapshot 为需要导出的历史时间点
| /dumpling --snapshot "2021-12-30 14:29:30" -u root -P 4000 -h 172.16.4.123 --filetype sql -t 6 -o ./bak -r 200000 -F 256MiB-B-p |
|---|
3、导入 dumpling 备份数据
六、多数副本丢失数据恢复
问题背景
TiDB 默认配置为 3 副本,每一个 Region 都会在集群中保存 3 份,它们之间通过 Raft 协议来选举 Leader 并同步数据。Raft 协议可以保证在数量小于副本数(注意,不是节点数)一半的节点挂掉或者隔离的情况下,仍然能够提供服务,并且不丢失任何数据。 对于 3 副本集群,挂掉一个节点除了可能会导致性能有抖动之外,可用性和正确性理论上不会受影响;
- 同时挂掉 2 个副本,一些 region 就会不可用,而且如果这 2 个副本无法完整地找回了,还存在永久丢失部分数据的可能。
- 同时挂掉3副本及以上,这个 Region 的数据就丢失了,无法恢复。 可以通过创建 1 个空 Region 来解决 Region 不可用的问题。
SQL语句出现region不可用的报错:
| select count(*) from t_user;ERROR 9005 (HY000): Region is unavailable |
|---|
2个副本丢失处理
1、启动 pd-ctl客户端
| pd-ctl -i -u http://172.16.134.133:2379 |
|---|
2、检查宕机的两台机器对应的store_id,输入store 指令,找出“state_name”: “Disconnected” 的store id。
| store“store”: {“id”: 5,“address”: “172.16.134.136:20160”,“labels”: [{“key”: “host”,“value”: “tikv3”}],“version”: “4.0.0-rc”,“status_address”: “172.16.134.136:20180”,“git_hash”: “f45d0c963df3ee4b1011caf5eb146cacd1fbbad8”,“start_timestamp”: 1594632461,“binary_path”: “/data1/tidb-deploy/tikv-20160/bin/tikv-server”,“last_heartbeat”: 1594700897622993541,“state_name”: “Disconnected”},… |
|---|
3、通过 pd-ctl config get 获取 region-schedule-limit、replica-schedule-limit、leader-schedule-limit、merge-schedule-limit
| » config show“region-schedule-limit”: 2048,“replica-schedule-limit”: 64,“leader-schedule-limit”: 4,“merge-schedule-limit”: 8, |
|---|
4、通过 pd-ctl config set 将这 4 个参数设为 0
关闭调度主要为将恢复过程中可能的异常情况降到最少,需在故障处理期间禁用相关的调度。
| » config set region-schedule-limit 0Success!» config set replica-schedule-limit 0Success!» config set leader-schedule-limit 0Success!» config set merge-schedule-limit 0Success! |
|---|
5、使用 pd-ctl 检查大于等于一半副本数在故障节点上的 Region,并记录它们的 ID(故障节点为store id 4,5):
| pd-ctl -u -d region --jq=’.regions[] | {id: .id, peer_stores: [.peers[].store_id] | select(length as total-length) }’ | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| » region --jq=".regions[] | {id: .id, peer_stores: [.peers[].store_id] | select(length as total-length) }"{“id”:3080,“peer_stores”:[4,6,5]}{“id”:18,“peer_stores”:[4,5,6]}{“id”:3084,“peer_stores”:[4,6,5]}{“id”:75,“peer_stores”:[4,5,6]}{“id”:34,“peer_stores”:[6,4,5]}{“id”:4005,“peer_stores”:[4,6,5]}{“id”:4009,“peer_stores”:[5,6,4]}{“id”:83,“peer_stores”:[4,5,6]}{“id”:3076,“peer_stores”:[4,5,6]}{“id”:4013,“peer_stores”:[5,4,6]}{“id”:10,“peer_stores”:[4,6,5]}{“id”:26,“peer_stores”:[4,6,5]}{“id”:59,“peer_stores”:[4,5,6]}{“id”:3093,“peer_stores”:[4,5,6]} | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
6、根据region ID,确认region属于哪张表,以备后续同步数据需要
| curl http://172.16.134.133:10080/regions/4009{“region_id”: 4009,“start_key”: “dIAAAAAAAABN”,“end_key”: “dIAAAAAAAABNX3KAAAAAAAt8fw==”,“frames”: [{“db_name”: “sbtest2”,“table_name”: “t_user”,“table_id”: 77,“is_record”: true,“record_id”: 752767}] |
|---|
7、在剩余正常的kv节点上执行停Tikv,以便释放文件锁
| tiup cluster stop tidb-test -R tikv |
|---|
8、在所有健康的节点上执行(操作需要确保健康的节点关闭了Tikv):
| ./tikv-ctl --data-dir /data1/tidb-data/tikv-20160 unsafe-recover remove-fail-stores -s 4,5 --all-regions |
|---|
当Region 比较少,则可以在给定 Region 的剩余副本上,移除掉所有位于故障节点上的 Peer,在这些 Region 的未发生掉电故障的机器上运行:
tikv-ctl --db /path/to/tikv-data/db unsafe-recover remove-fail-stores -s <s1,s2> -r <r1,r2,r3>,对于region较多的情况,此操作则较为繁琐。
9、停止PD节点:
| tiup cluster stop tidb-test -R pd |
|---|
10、重启启动PD tikv节点:
| tiup cluster start tidb-test -R pd,tikv |
|---|
11、检查没有处于leader状态的region(要保持没有),这里没有发现没有leader状态的region。
| pd-ctl -i -u http://172.16.134.133:2379» region --jq ‘.regions[]|select(has(“leader”)|not)|{id: .id,peer_stores: [.peers[].store_id]}’» | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
12、重新修改pd参数
| pd-ctl -i -u http://172.16.134.133:2379» config set region-schedule-limit 2048Success!» config set replica-schedule-limit 64Success!» config set leader-schedule-limit 4Success!» config set merge-schedule-limit 8Success! |
|---|
13、检查查询数据是否正常
| select count() from t_user;±---------+| count() |±---------+| 3000000 |±---------+1 row in set (9.95 sec) | | ----------------------------------------------------------------------------------------------------------------- |
恢复完成,业务检查确认数据。
3个副本丢失处理
| 数据不能恢复,创建空 Region 解决 Unavailable 报错 |
|---|
1、3个tikv节点不可用,报错 Region is unavailable
| MySQL [sbtest2]> select count(*) from t_user;ERROR 9005 (HY000): Region is unavailable |
|---|
2、检查宕机的机器对应的store_id
| pd-ctl -i -u http://172.16.134.133:2379» store |
|---|
3、通过 pd-ctl config get 获取 region-schedule-limit、replica-schedule-limit、leader-schedule-limit、merge-schedule-limit并通过 pd-ctl config set 将这 4 个参数设为 0
| config set region-schedule-limit 0Success!» config set replica-schedule-limit 0Success!» config set leader-schedule-limit 0Success!» config set merge-schedule-limit 0Success! |
|---|
4、使用 pd-ctl 检查大于等于一半副本数在故障节点上的 Region,并记录它们的 ID(故障节点为store id 1,5,6)
| region --jq=".regions[] | {id: .id, peer_stores: [.peers[].store_id] | select(length as total-length) }"{“id”:3089,“peer_stores”:[5,4,6]}{“id”:47,“peer_stores”:[4,5,6]}{“id”:75,“peer_stores”:[4,5,6]}{“id”:30,“peer_stores”:[6,4,5]}{“id”:135,“peer_stores”:[6,4,5]}{“id”:4017,“peer_stores”:[6,7,5]}{“id”:67,“peer_stores”:[4,5,1]}{“id”:2289,“peer_stores”:[4,6,5]}{“id”:18,“peer_stores”:[6,4,5]}{“id”:39,“peer_stores”:[6,4,5]}{“id”:51,“peer_stores”:[4,6,5]}{“id”:10,“peer_stores”:[4,5,6]}{“id”:14,“peer_stores”:[6,5,4]}{“id”:83,“peer_stores”:[6,4,5]}{“id”:59,“peer_stores”:[6,4,5]}{“id”:6768,“peer_stores”:[1,6,4]}{“id”:22,“peer_stores”:[4,5,6]}{“id”:26,“peer_stores”:[6,4,5]}{“id”:43,“peer_stores”:[6,4,5]}{“id”:131,“peer_stores”:[6,4,5]}{“id”:4009,“peer_stores”:[6,1,5]}{“id”:2,“peer_stores”:[7,6,5]}{“id”:63,“peer_stores”:[4,5,1]}{“id”:87,“peer_stores”:[6,4,5]}{“id”:6734,“peer_stores”:[6,1,5]}{“id”:3080,“peer_stores”:[6,4,5]}{“id”:3084,“peer_stores”:[6,4,5]}{“id”:3076,“peer_stores”:[6,4,5]}{“id”:34,“peer_stores”:[6,4,5]}{“id”:127,“peer_stores”:[6,4,5]}{“id”:3070,“peer_stores”:[6,4,5]} | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
5、在剩余正常的kv节点上执行停Tikv的操作:
| tiup cluster stop tidb-test -R tikv |
|---|
6、在所有健康的节点上执行(操作需要确保健康的节点关闭了Tikv):
| ./tikv-ctl --data-dir /data1/tidb-data/tikv-20160/ unsafe-recover remove-fail-stores -s 1,5,6 --all-regions |
|---|
7、停止PD节点:
| tiup cluster stop tidb-test -R pd |
|---|
8、重启启动PD tikv节点:
| tiup cluster start tidb-test -R pd,tikv |
|---|
9、检查没有处于leader状态的region
| pd-ctl -i -u http://172.16.134.133:2379» region --jq ‘.regions[]|select(has(“leader”)|not)|{id: .id,peer_stores: [.peers[].store_id]}’{“id”:4009,“peer_stores”:[6,1,5]}{“id”:6734,“peer_stores”:[6,1,5]} | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
这里发现有两个region处于没有leader的状态
10、 根据region ID,确认region属于哪张表,以备后续同步数据需要。
| curl http://172.16.134.133:10080/regions/4009{“region_id”: 4009,“start_key”: “dIAAAAAAAABN”,“end_key”: “dIAAAAAAAABNX3KAAAAAAAt8fw==”,“frames”: [{“db_name”: “sbtest2”,“table_name”: “t_user”,“table_id”: 77,“is_record”: true,“record_id”: 752767}] |
|---|
两个region ID均属于同一张表。
11、创建空 Region 解决 Unavailable 报错。任选一个 Store,关闭上面的 TiKV,然后执行 recreate-region
| ./tikv-ctl --data-dir /data1/tidb-data/tikv-20160/db recreate-region -p ‘172.16.134.133:2379’ -r 4009./tikv-ctl --data-dir /data1/tidb-data/tikv-20160/db recreate-region -p ‘172.16.134.133:2379’ -r 6734 |
|---|
12、停止PD节点:
| tiup cluster stop tidb-test -R pd |
|---|
13、重启启动PD tikv节点
| tiup cluster start tidb-test -R pd,tikv |
|---|
14、检查没有处于leader状态的region(要保持没有):
| pd-ctl -i -u http://172.16.134.133:2379» region --jq ‘.regions[]|select(has(“leader”)|not)|{id: .id,peer_stores: [.peers[].store_id]}’» | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
15、重新修改PD的参数
| pd-ctl -i -u http://172.16.134.133:2379» config set region-schedule-limit 2048Success!» config set replica-schedule-limit 64Success!» config set leader-schedule-limit 4Success!» config set merge-schedule-limit 8Success! |
|---|
16、对于所有副本丢失,无法恢复数据,从其他途径恢复数据如:
-
备份恢复
-
从上游重新导入
-
切换到从集群