CategoryResourceRepost/极客时间专栏/SQL必知必会/第二章:SQL性能优化篇/36丨数据库没有备份,没有使用Binlog的情况下,如何恢复数据?.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

159 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<audio id="audio" title="36丨数据库没有备份没有使用Binlog的情况下如何恢复数据" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4d/19/4d3a8cabc41a443b20e7bbba50646e19.mp3"></audio>
我们上节课讲解了MySQL的复制技术通过主从同步可以实现读写分离热备份让服务器更加高可用。MySQL的复制主要是通过Binlog来完成的Binlog记录了数据库更新的事件从库I/O线程会向主库发送Binlog更新的请求同时主库二进制转储线程会发送Binlog给从库作为中继日志进行保存然后从库会通过中继日志重放完成数据库的同步更新。这种同步操作是近乎实时的同步然而也有人为误操作情况的发生比如DBA人员为了方便直接在生产环境中对数据进行操作或者忘记了当前是在开发环境还是在生产环境中就直接对数据库进行操作这样很有可能会造成数据的丢失情况严重时误操作还有可能同步给从库实时更新。不过我们依然有一些策略可以防止这种误操作比如利用延迟备份的机制。延迟备份最大的作用就是避免这种“手抖”的情况让我们在延迟从库进行误操作前停止下来进行数据库的恢复。
当然如果我们对数据库做过时间点备份也可以直接恢复到该时间点。不过我们今天要讨论的是一个特殊的情况也就是在没做数据库备份没有开启使用Binlog的情况下尽可能地找回数据。
今天的内容主要包括以下几个部分:
1. InnoDB存储引擎中的表空间是怎样的两种表空间存储方式各有哪些优缺点
1. 如果.ibd文件损坏了数据该如何找回
1. 如何模拟InnoDB文件的损坏与数据恢复
## InnoDB存储引擎的表空间
InnoDB存储引擎的文件格式是.ibd文件数据会按照表空间tablespace进行存储分为共享表空间和独立表空间。如果想要查看表空间的存储方式我们可以对`innodb_file_per_table`变量进行查询,使用`show variables like 'innodb_file_per_table';`。ON表示独立表空间而OFF则表示共享表空间。
<img src="https://static001.geekbang.org/resource/image/7d/0d/7dc5aae75a7d5d2c599c7cb8dd97440d.png" alt=""><br>
如果采用共享表空间的模式InnoDB存储的表数据都会放到共享表空间中也就是多个数据表共用一个表空间同时表空间也会自动分成多个文件存放到磁盘上。这样做的好处在于单个数据表的大小可以突破文件系统大小的限制最大可以达到64TB也就是InnoDB存储引擎表空间的上限。不足也很明显多个数据表存放到一起结构不清晰不利于数据的找回同时将所有数据和索引都存放到一个文件中也会使得共享表空间的文件很大。
采用独立表空间的方式可以让每个数据表都有自己的物理文件也就是table_name.ibd的文件在这个文件中保存了数据表中的数据、索引、表的内部数据字典等信息。它的优势在于每张表都相互独立不会影响到其他数据表存储结构清晰利于数据恢复同时数据表还可以在不同的数据库之间进行迁移。
## 如果.ibd文件损坏了数据如何找回
如果我们之前没有做过全量备份也没有开启Binlog那么我们还可以通过.ibd文件进行数据恢复采用独立表空间的方式可以很方便地对数据库进行迁移和分析。如果我们误删除DELETE某个数据表或者某些数据行也可以采用第三方工具回数据。
我们这里可以使用Percona Data Recovery Tool for InnoDB工具能使用工具进行修复是因为我们在使用DELETE的时候是逻辑删除。我们之前学习过InnoDB的页结构在保存数据行的时候还有个删除标记位对应的是页结构中的delete_mask属性该属性为1的时候标记了记录已经被逻辑删除实际上并不是真的删除。不过当有新的记录插入的时候被删除的行记录可能会被覆盖掉。所以当我们发生了DELETE误删除的时候一定要第一时间停止对误删除的表进行更新和写入及时将.ibd文件拷贝出来并进行修复。
如果已经开启了Binlog就可以使用闪回工具比如mysqlbinlog或者binlog2sql从工具名称中也能看出来它们都是基于Binlog来做的闪回。原理就是因为Binlog文件本身保存了数据库更新的事件Event通过这些事件可以帮我们重现数据库的所有更新变化也就是Binlog回滚。
下面我们就来看下没有做过备份也没有开启Binlog的情况下如果.ibd文件发生了损坏如何通过数据库自身的机制来进行数据恢复。
实际上InnoDB是有自动恢复机制的如果发生了意外InnoDB可以在读取数据表时自动修复错误。但有时候.ibd文件损坏了会导致数据库无法正常读取数据表这时我们就需要人工介入调整一个参数这个参数叫做`innodb_force_recovery`。
我们可以通过命令`show variables like 'innodb_force_recovery';`来查看当前参数的状态你能看到默认为0表示不进行强制恢复。如果遇到错误比如ibd文件中的数据页发生损坏则无法读取数据会发生MySQL宕机的情况此时会将错误日志记录下来。
<img src="https://static001.geekbang.org/resource/image/6e/ba/6edf81f6402311ca7f8ee8619b656fba.png" alt=""><br>
`innodb_force_recovery`参数一共有7种状态除了默认的0以外还可以为1-6的取值分别代表不同的强制恢复措施。
当我们需要强制恢复的时候,可以将`innodb_force_recovery`设置为1表示即使发现了损坏页也可以继续让服务运行这样我们就可以读取数据表并且对当前损坏的数据表进行分析和备份。
通常`innodb_force_recovery`参数设置为1只要能正常读取数据表即可。但如果参数设置为1之后还无法读取数据表我们可以将参数逐一增加比如2、3等。一般来说不需要将参数设置到4或以上因为这有可能对数据文件造成永久破坏。另外当`innodb_force_recovery`设置为大于0时相当于对InnoDB进行了写保护只能进行SELECT读取操作还是有限制的读取对于WHERE条件以及ORDER BY都无法进行操作。
当我们开启了强制恢复之后,数据库的功能会受到很多限制,我们需要尽快把有问题的数据表备份出来,完成数据恢复操作。整体的恢复步骤可以按照下面的思路进行:
1.使用`innodb_force_recovery`启动服务器
将`innodb_force_recovery`参数设置为1启动数据库。如果数据表不能正常读取需要调大参数直到能读取数据为止。通常设置为1即可。
2.备份数据表
在备份数据之前需要准备一个新的数据表这里需要使用MyISAM存储引擎。原因很简单InnoDB存储引擎已经写保护了无法将数据备份出来。然后将损坏的InnoDB数据表备份到新的MyISAM数据表中。
3.删除旧表,改名新表
数据备份完成之后我们可以删除掉原有损坏的InnoDB数据表然后将新表进行改名。
4.关闭`innodb_force_recovery`,并重启数据库
`innodb_force_recovery`大于1的时候会有很多限制我们需要将该功能关闭然后重启数据库并且将数据表的MyISAM存储引擎更新为InnoDB存储引擎。
## InnoDB文件的损坏与恢复实例
我们刚才说了InnoDB文件损坏时的人工操作过程下面我们用一个例子来模拟下。
### 生成InnoDB数据表
为了简便我们创建一个数据表t1只有id一个字段类型为int。使用命令`create table t1(id int);`即可。
<img src="https://static001.geekbang.org/resource/image/0c/34/0c2790f539d4f35aec6dd0703b059134.png" alt=""><br>
然后创建一个存储过程帮我们生成一些数据:
```
BEGIN
-- 当前数据行
DECLARE i INT DEFAULT 0;
-- 最大数据行数
DECLARE max_num INT DEFAULT 100;
-- 关闭自动提交
SET autocommit=0;
REPEAT
SET i=i+1;
-- 向t1表中插入数据
INSERT INTO t1(id) VALUES(i);
UNTIL i = max_num
END REPEAT;
-- 提交事务
COMMIT;
END
```
然后我们运行`call insert_t1()`这个存储过程帮我们插入了100条数据这样我们就有了t1.ibd这个文件。
### 模拟损坏.ibd文件
实际工作中我们可能会遇到各种各样的情况,比如.ibd文件损坏等如果遇到了数据文件的损坏MySQL是无法正常读取的。在模拟损坏.ibd文件之前我们需要先关闭掉MySQL服务然后用编辑器打开t1.ibd类似下图所示
<img src="https://static001.geekbang.org/resource/image/d3/7c/d34734f6dfbf0e01cf4a40bb549e147c.png" alt=""><br>
文件是有二进制编码的看不懂没有关系我们只需要破坏其中的一些内容即可比如我在t1.ibd文件中删除了2行内容文件大部分内容为0我们在文件中间部分找到一些非0的取值然后删除其中的两行4284行与4285行原ibd文件和损坏后的ibd文件见[GitHub](https://github.com/cystanford/innodb_force_recovery)[地址](https://github.com/cystanford/innodb_force_recovery)。其中t1.ibd为创建的原始数据文件,t1-损坏.ibd为损坏后的数据文件你需要自己创建t1数据表然后将t1-损坏.ibd拷贝到本地并改名为t1.ibd
然后我们保存文件,这时.ibd文件发生了损坏如果我们没有打开`innodb_force_recovery`那么数据文件无法正常读取。为了能读取到数据表中的数据我们需要修改MySQL的配置文件找到`[mysqld]`的位置,然后再下面增加一行`innodb_force_recovery=1`。
<img src="https://static001.geekbang.org/resource/image/a3/94/a3a226bbd1b435393039b75618be6e94.png" alt="">
### 备份数据表
当我们设置`innodb_force_recovery`参数为1的时候可以读取到数据表t1中的数据但是数据不全。我们使用`SELECT * FROM t1 LIMIT 10;`读取当前前10条数据。
<img src="https://static001.geekbang.org/resource/image/76/0a/761a5495f9769de35f60f112e4d94b0a.png" alt=""><br>
但是如果我们想要完整的数据,使用`SELECT * FROM t1 LIMIT 100;`就会发生如下错误。
<img src="https://static001.geekbang.org/resource/image/62/93/623a6cd6987f5ac11aad432257edcc93.png" alt=""><br>
这是因为读取的部分包含了已损坏的数据页我们可以采用二分查找判断数据页损坏的位置。这里我们通过实验可以得出只有最后一个记录行收到了损坏而前99条记录都可以正确读出具体实验过程省略
这样我们就能判断出来有效的数据行的位置从而将它们备份出来。首先我们创建一个相同的表结构t2存储引擎设置为MyISAM。我刚才讲过这里使用MyISAM存储引擎是因为在`innodb_force_recovery=1`的情况下无法对innodb数据表进行写数据。使用命令`CREATE TABLE t2(id int) ENGINE=MyISAM;`。
然后我们将数据表t1中的前99行数据复制给t2数据表使用
```
INSERT INTO t2 SELECT * FROM t1 LIMIT 99;
```
<img src="https://static001.geekbang.org/resource/image/a9/38/a91d4a2d259bba1c2aeb9a5b95619238.png" alt=""><br>
我们刚才讲过在分析t1数据表的时候无法使用WHERE以及ORDER BY等子句这里我们可以实验一下如果想要查询id&lt;10的数据行都有哪些那么会发生如下错误。原因是损坏的数据页无法进行条件判断。
<img src="https://static001.geekbang.org/resource/image/fc/9d/fc00a89e22a301826bbdd7b37d7b7c9d.png" alt="">
### 删除旧表,改名新表
刚才我们已经恢复了大部分的数据。虽然还有一行记录没有恢复,但是能找到绝大部分的数据也是好的。然后我们就需要把之前旧的数据表删除掉,使用`DROP TABLE t1;`。
<img src="https://static001.geekbang.org/resource/image/f3/47/f30d79228572382810fd388ded1ff447.png" alt=""><br>
更新表名将数据表名称由t2改成t1使用`RENAME TABLE t2 to t1;`。
<img src="https://static001.geekbang.org/resource/image/23/98/23af300e09c2fd5afafe1cec849d7f98.png" alt=""><br>
将新的数据表t1存储引擎改成InnoDB不过直接修改的话会报如下错误
<img src="https://static001.geekbang.org/resource/image/d1/a6/d102e7ec02a228529b26bd070601b9a6.png" alt="">
### 关闭`innodb_force_recovery`,并重启数据库
因为上面报错所以我们需要将MySQL配置文件中的`innodb_force_recovery=1`删除掉然后重启数据库。最后将t1的存储引擎改成InnoDB即可使用`ALTER TABLE t1 engine = InnoDB;`。
<img src="https://static001.geekbang.org/resource/image/ab/1b/abc06822471b4eb4036de1504840df1b.png" alt="">
## 总结
我们刚才人工恢复了损坏的ibd文件中的数据虽然没有100%找回但是相比于束手无措来说已经是不幸中的万幸至少我们还可以把正确的数据页中的记录成功备份出来尽可能恢复原有的数据表。在这个过程中相信你应该对ibd文件以及InnoDB自身的强制恢复Force Recovery机制有更深的了解。
数据表损坏以及人为的误删除都不是我们想要看到的情况但是我们不能指望运气或者说我们不能祈祷这些事情不会发生。在遇到这些情况的时候应该通过机制尽量保证数据库的安全稳定运行。这个过程最主要的就是应该及时备份并且开启二进制日志这样当有误操作的时候就可以通过数据库备份以及Binlog日志来完成数据恢复。同时采用延迟备份的策略也可以尽量抵御误操作。总之及时备份是非常有必要的措施同时我们还需要定时验证备份文件的有效性保证备份文件可以正常使用。
如果你遇到了数据库ibd文件损坏的情况并且没有采用任何的备份策略可以尝试使用InnoDB的强制恢复机制启动MySQL并且将损坏的数据表转储到MyISAM数据表中尽可能恢复已有的数据。总之机制比人为更靠谱我们要为长期的运营做好充足的准备。一旦发生了误操作这种紧急情况不要慌张及时采取对应的措施才是最重要的。
<img src="https://static001.geekbang.org/resource/image/0b/b0/0bce932aae90fbd40a72454d84fad9b0.png" alt=""><br>
今天的内容到这里就结束了,我想问问,在日常工作中,你是否遇到过误操作的情况呢?你又是如何解决的?除了我上面介绍的机制外,还有哪些备份的机制可以增强数据的安全性?
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流一下。