CategoryResourceRepost/极客时间专栏/MySQL 必知必会/进阶篇/20 | 日志(下):系统故障,如何恢复数据?.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

473 lines
22 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="20 | 日志(下):系统故障,如何恢复数据?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/92/28/921d3fba4fc361633647b8e679eac928.mp3"></audio>
你好,我是朱晓峰。
上节课咱们学习了通用查询日志、慢查询日志和错误日志它们可以帮助我们快速定位系统问题。但实际上日志也可以帮助我们找回由于误操作而丢失的数据比如二进制日志binary log、中继日志relay log、回滚日志undo log和重做日志redo log
这节课我们就来学习下这4种日志。
## 二进制日志
**二进制日志主要记录数据库的更新事件**,比如创建数据表、更新表中的数据、数据更新所花费的时长等信息。通过这些信息,我们可以再现数据更新操作的全过程。而且,由于日志的延续性和时效性,我们还可以利用日志,完成无损失的数据恢复和主从服务器之间的数据同步。
可以说,二进制日志是进行数据恢复和数据复制的利器。所以,接下来我就结合一个实际案例,重点给你讲一讲怎么操作它。
### 如何操作二进制日志?
操作二进制日志,主要包括查看、刷新二进制日志,用二进制日志恢复数据,以及删除二进制日志。
**1.查看二进制日志**
查看二进制日志主要有3种情况分别是查看当前正在写入的二进制日志、查看所有的二进制日志和查看二进制日志中的所有数据更新事件。
查看当前正在写入的二进制日志的SQL语句是
```
SHOW MASTER STATUS;
```
我们可以通过这条语句,查看当前正在写入的二进制日志的名称和当前写入的位置:
```
mysql&gt; SHOW MASTER STATUS;
+----------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+----------------------+----------+--------------+------------------+-------------------+
| GJTECH-PC-bin.000011 | 2207 | | | |
+----------------------+----------+--------------+------------------+-------------------+ -- 当前正在写入的二进制日志是“GJTECH-PC-bin.000011”当前的位置是2207。
1 row in set (0.00 sec)
```
查看所有的二进制日志的SQL语句是
```
SHOW BINARY LOGS;
```
查看二进制日志中所有数据更新事件的SQL语句是
```
SHOW BINLOG EVENTS IN 二进制文件名;
```
**2.刷新二进制日志**
刷新二进制日志的SQL语句是
```
FLUSH BINARY LOGS;
```
这条语句的意思是关闭服务器正在写入的二进制日志文件并重新打开一个新文件文件名的后缀在现有的基础上加1。
**3.用二进制日志恢复数据**
我们可以用mysqlbinlog工具进行数据恢复
```
mysqlbinlog start-positon=xxx end-position=yyy 二进制文件名 | mysql -u 用户 -p
```
这条命令的意思是执行二进制日志中从位置xxx开始到yyy截止的所有数据更新操作。这里的截止位置也可以不写意思是从位置xxx开始执行二进制文件中的所有数据更新操作。
**4.删除二进制日志**
如果我们已经把日志文件保存到了安全的地方就可以通过下面的SQL语句删除所有二进制日志文件以释放磁盘空间
```
mysql&gt; RESET MASTER;
Query OK, 0 rows affected (0.20 sec)
mysql&gt; SHOW BINARY LOGS;
+----------------------+-----------+-----------+
| Log_name | File_size | Encrypted |
+----------------------+-----------+-----------+
| GJTECH-PC-bin.000001 | 156 | No |
+----------------------+-----------+-----------+
1 row in set (0.00 sec)
```
结果显示所有二进制日志文件都被删除了MySQL从头准备了一个“.000001”为后缀的新的二进制日志文件。
我们也可以通过SQL语句删除比指定二进制日志文件编号小的所有二进制日志文件
```
mysql&gt; PURGE MASTER LOGS TO 'GJTECH-PC-bin.000005';
Query OK, 0 rows affected (0.02 sec)
```
好了,知道了二进制日志的操作方法,下面我们借助一个案例,来实操一下。我们来看看怎么通过二进制日志恢复数据,避免因故障或异常等导致数据损失。
### 案例讲解
假设数据库demo中有一个商品信息表demo.goodsmaster我先对数据库demo做了一个全量备份。所谓的全量备份就是指对数据库中存储的全部数据进行备份。备份完成之后我又在商品信息表中插入了新数据。
这个时候数据库demo出现异常数据全部丢失。现在咱们需要把所有的数据包括备份前的数据和备份之后新插入的数据都恢复回来。我来介绍下具体的操作步骤。
商品信息表的信息如下所示:
<img src="https://static001.geekbang.org/resource/image/98/83/980b731a6ab550894479b1f3382a9683.jpeg" alt="">
可以看到表中有一条记录编号是1的商品名称是“书”售价是89元。
**第一步,做数据库备份。**
你可以用MySQL的数据备份工具mysqldump来备份数据。这个工具的语法结构如下所示
```
mysqldump -u 用户 -p 密码 数据库 &gt; 备份文件
```
在这个场景中我们可以使用mysqldump工具把数据库demo中的全部信息备份到文件“mybackup.sql”中来完成对数据库demo的全量备份
```
H:\&gt;mysqldump -u root -p demo &gt; mybackup.sql
Enter password: *****
```
这个命令的意思是把数据库demo中的全部数据备份到文件mybackup.sql中。
**第二步用“FLUSH BINARY LOGS;”语句刷新一下日志。**
```
mysql&gt; FLUSH BINARY LOGS;
Query OK, 0 rows affected (0.06 sec)
```
这步操作的目的是:产生一个新的二进制日志文件,使这个文件只保存数据备份之后的数据更新事件,这样可以方便我们查看文件的内容。
**第三步,给商品信息表插入一条新的数据记录“笔”。**
```
mysql&gt; INSERT INTO demo.goodsmaster
-&gt; (
-&gt; itemnumber,
-&gt; barcode,
-&gt; goodsname,
-&gt; salesprice
-&gt; )
-&gt; VALUES
-&gt; (
-&gt; 2,
-&gt; '0002',
-&gt; '笔',
-&gt; 3
-&gt; );
Query OK, 1 row affected (0.03 sec)
```
这样我们就增加了一个新的商品“笔”。
现在,我们来查看一下数据表里的内容:
```
mysql&gt; SELECT * FROM demo.goodsmaster;
+------------+---------+-----------+------------+
| itemnumber | barcode | goodsname | salesprice |
+------------+---------+-----------+------------+
| 1 | 0001 | 书 | 89.00 |
| 2 | 0002 | 笔 | 3.00 |
+------------+---------+-----------+------------+
2 rows in set (0.00 sec)
```
结果显示我们有了2条记录分别是“书”和“笔”。
假设这个时候,系统突然宕机,数据库无法启动,为了使系统恢复正常,我们重启机器,重新创建数据库,并且需要进行数据恢复。
**第四步,准备从备份文件恢复数据。**
每当服务器启动、刷新二进制日志或者二进制日志大小超过系统变量 max_binlog_size指定的值时系统就会生成一个新的二进制日志文件。
我们先查看一下机器上的二进制日志文件,以确定哪个文件是我们正在使用的。
```
mysql&gt; SHOW BINARY LOGS;
+----------------------+-----------+-----------+
| Log_name | File_size | Encrypted |
+----------------------+-----------+-----------+
| GJTECH-PC-bin.000005 | 179 | No |
| GJTECH-PC-bin.000006 | 113316452 | No |
| GJTECH-PC-bin.000007 | 12125 | No |
| GJTECH-PC-bin.000008 | 1544 | No |
| GJTECH-PC-bin.000009 | 207 | No |
| GJTECH-PC-bin.000010 | 1758 | No |
| GJTECH-PC-bin.000011 | 2207 | No |
| GJTECH-PC-bin.000012 | 462 | No |
+----------------------+-----------+-----------+
12 rows in set (0.01 sec)
```
结果显示最新的也就是记录了数据插入操作的二进制日志文件是“GJTECH-PC-bin.000012”,这就是接下来我们要用的日志文件。
接着,我们来刷新一下二进制日志文件,这样做的目的是防止后面数据恢复的事件全都被写入这个二进制日志文件,妨碍我们理解文件的内容。
```
mysql&gt; FLUSH BINARY LOGS;
Query OK, 0 rows affected (0.08 sec)
```
现在,我们查看一下当前正在写入的二进制文件和位置,确认一下系统是否创建了新的二进制日志文件:
```
mysql&gt; SHOW MASTER STATUS;
+----------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+----------------------+----------+--------------+------------------+-------------------+
| GJTECH-PC-bin.000013 | 156 | | | |
+----------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
```
结果显示当前在使用的二进制日志文件是“GJTECH-PC-bin.000013”,这样保存增量数据的二进制日志文件就不会受到后面操作的影响了。
下面我来删除并重建一个数据库demo给你演示一下如何使用二进制日志恢复数据。
```
mysql&gt; DROP DATABASE demo;
Query OK, 1 row affected (0.07 sec)
mysql&gt; CREATE DATABASE demo;
Query OK, 1 row affected (0.01 sec)
```
通过上面的操作就有了一个空数据库demo。接下来就可以恢复数据了。
**第五步,从备份恢复数据。**
可以通过mysql命令来恢复数据语法结构如下
```
mysql -u 用户 -p 密码 数据库名称 &lt; 备份文件
```
现在我用刚才的备份文件,通过下面的指令来恢复数据:
```
H:\&gt;mysql -u root -p demo&lt;mybackup.sql
Enter password: *****
```
这个指令的意思是在数据库demo中执行备份文件“mybackup.sql”中的所有SQL操作这样就可以把demo中的数据恢复到备份时的状态了。
我们,来看一下现在商品信息表中的数据:
```
mysql&gt; SELECT * FROM demo.goodsmaster;
+------------+---------+-----------+------------+
| itemnumber | barcode | goodsname | salesprice |
+------------+---------+-----------+------------+
| 1 | 0001 | 书 | 89.00 |
+------------+---------+-----------+------------+
1 row in set (0.00 sec)
```
结果显示,只有一条数据记录“书”,那么,怎么才能把我们备份之后插入的那条数据“笔”给找回呢?这个时候,就要进入下一步,使用二进制日志了。
**第六步从保存增量信息的二进制日志文件“GJTECH-PC-bin.000012”中恢复增量数据。**
<img src="https://static001.geekbang.org/resource/image/c1/64/c1d720d4405e479ba71eca27d5a15e64.png" alt="">
通过查看二进制日志中的事件你会发现写入第二条记录的时候MySQL使用了一个事务操作起始位置是306截止位置是462。这样就可以用mysqlbinlog工具进行数据恢复了。日志名称是“C:\ProgramData\MySQL\MySQL Server 8.0\Data\GJTECH-PC-bin.000012”读取日志的起始位置是306。
```
H:\&gt;mysqlbinlog --start-position=306 &quot;C:\ProgramData\MySQL\MySQL Server 8.0\Data\GJTECH-PC-bin.000012&quot; | mysql -u root -p
Enter password: *****
```
现在我们查看一下商品信息表,确认一下备份之后插入的商品数据记录是不是恢复回来了。
```
mysql&gt; SELECT * FROM demo.goodsmaster;
+------------+---------+-----------+------------+
| itemnumber | barcode | goodsname | salesprice |
+------------+---------+-----------+------------+
| 1 | 0001 | 书 | 89.00 |
| 2 | 0002 | 笔 | 3.00 |
+------------+---------+-----------+------------+
2 rows in set (0.00 sec)
```
结果显示,备份之后插入的记录“笔”也找回来了。
到这里,二进制日志恢复数据的功能我就介绍完了。需要提醒你注意的是,在实际工作中,用二进制日志文件找回数据时经常会遇到问题,主要就是不容易找准起始位置或者截止位置。找早了,会导致数据冲突、重复;找晚了,又会丢失数据。所以,我建议你在数据备份结束之后,把当前的二进制日志位置记录下来,存放在一个安全的地方,这关系到数据恢复的完整性,一定不要怕麻烦。
二进制日志还有一个重要的功能,就是在主从服务器的架构中,把主服务器的操作复制到从服务器。而这个操作要借助中继日志一起完成。
## 中继日志
**中继日志只在主从服务器架构的从服务器上存在**。从服务器为了与主服务器保持一致,要从主服务器读取二进制日志的内容,并且把读取到的信息写入本地的日志文件中,这个从服务器本地的日志文件就叫中继日志。然后,从服务器读取中继日志,并根据中继日志的内容对从服务器的数据进行更新,完成主从服务器的数据同步。
搭建好主从服务器之后中继日志默认会保存在从服务器的数据目录C:\ProgramData\MySQL\MySQL Server 8.0\Data文件名的格式是从服务器名-relay-bin.序号。
中继日志还有一个索引文件:从服务器名-relay-bin.index用来定位当前正在使用的中继日志。
中继日志与二进制日志的格式相同可以用mysqlbinlog工具进行查看。下面是中继日志的一个片段
```
SET TIMESTAMP=1618558728/*!*/;
BEGIN
/*!*/;
# at 950
#210416 15:38:48 server id 1 end_log_pos 832 CRC32 0xcc16d651 Table_map: `demo`.`test` mapped to number 91
# at 1000
#210416 15:38:48 server id 1 end_log_pos 872 CRC32 0x07e4047c Delete_rows: table id 91 flags: STMT_END_F -- server id 1 是主服务器,意思是主服务器删了一行数据
BINLOG '
CD95YBMBAAAAMgAAAEADAAAAAFsAAAAAAAEABGRlbW8ABHRlc3QAAQMAAQEBAFHWFsw=
CD95YCABAAAAKAAAAGgDAAAAAFsAAAAAAAEAAgAB/wABAAAAfATkBw==
'/*!*/;
# at 1040
```
这一段的意思是主服务器“server id 1”对表demo.test进行了2步操作
1. 定位到表demo.test编号是91的记录日志位置是832
1. 删除编号是91的记录日志位置是872。
关于中继日志,有一个很容易踩到的坑。如果从服务器宕机,有的时候为了系统恢复,你要重装操作系统,这样就可能会导致你的服务器名称与之前不同。而中继日志的名称里面是包含从服务器名的。因此,在这种情况下,就可能导致你恢复从服务器的时候,无法从宕机前的中继日志里读取数据,以为是日志文件损坏了,其实是文件名不对了。解决的方法也很简单,只要把从服务器的名称改回之前的名称就可以了。
下面我再介绍一下回滚日志。
## 回滚日志
**回滚日志的作用是进行事务回滚**
当事务执行的时候,回滚日志中记录了事务中每次数据更新前的状态。当事务需要回滚的时候,可以通过读取回滚日志,恢复到指定的位置。另一方面,回滚日志也可以让其他的事务读取到这个事务对数据更改之前的值,从而确保了其他事务可以不受这个事务修改数据的影响。
回滚日志的设置是啥样的呢?我们来学习下相关变量值,包括文件大小、所在的文件夹、是否加密、是否自动截断回收以及是否有独立的表空间等。这些都是我们了解事务回滚的机制的关键。
```
mysql&gt; SHOW VARIABLES LIKE '%innodb_max_undo_log_size%';
+--------------------------+------------+
| Variable_name | Value |
+--------------------------+------------+
| innodb_max_undo_log_size | 1073741824 |
+--------------------------+------------+
1 row in set, 1 warning (0.00 sec)
```
变量“innodb_max_undo_log_size”的意思是单个回滚日志最大可占用1G字节存储空间。
下面几个变量定义了回滚日志所在的文件夹、是否加密、是否自动截断回收空间和是否有独立的表空间等。
```
mysql&gt; SHOW VARIABLES LIKE '%innodb_undo%';
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| innodb_undo_directory | .\ | -- 表示回滚日志的存储目录是数据目录。
| innodb_undo_log_encrypt | OFF | -- 表示回滚日志不加密。
| innodb_undo_log_truncate | ON | -- 表示回滚日志是否自动截断回收,前提是设置了独立表空间。
| innodb_undo_tablespaces | 2 | -- 表示回滚日志有自己的独立表空间而不是在共享表空间ibdata文件中。
+--------------------------+-------+
4 rows in set, 1 warning (0.00 sec)
```
这里的结果显示了这4个变量的默认值。下面我来分别解释一下。
- innodb_undo_directory=.\ 表示回滚日志的存储目录是数据目录数据目录的位置可以通过查询变量“datadir”来查看。
- innodb_undo_log_encrypt = OFF表示回滚日志不加密。
- innodb_undo_log_truncate = ON表示回滚日志是否自动截断回收这个变量有效的前提是设置了独立表空间。
- innodb_undo_tablespaces = 2表示回滚日志有自己的独立表空间而不是在共享表空间ibdata文件中。
下面的截图显示了回滚日志的存储目录,以及在文件夹中的名称等信息:
<img src="https://static001.geekbang.org/resource/image/90/38/90ebac7eed19314e6fc5928b92a56b38.png" alt="">
最后我来介绍一下MySQL的重做日志。
## 重做日志
重做日志是存储在磁盘上的一种日志文件主要有2个作用。
1. 在系统遇到故障的恢复过程中,可以修复被未完成的事务修改的数据。
1. MySQL为了提高数据存取的效率减少磁盘操作的频率对数据的更新操作不会立即写到磁盘上而是把数据更新先保存在内存中积累到一定程度再集中进行磁盘读写操作。这样就存在一个问题一旦出现宕机或者停电等异常情况内存中保存的数据更新操作可能会丢失。这个时候就可以通过读取重做日志中记录的数据更新操作把没来得及写到磁盘上的数据更新写到磁盘上确保数据的完整性。
我们可以通过系统变量的值,了解重做日志所在的文件夹和文件的数量。这些是我们进一步了解系统运行机制的必要条件,有助于我们开发出高效的数据库应用。
```
mysql&gt; SHOW VARIABLES LIKE '%innodb_log_files_in_group%';
+---------------------------+-------+
| Variable_name | Value |
+---------------------------+-------+
| innodb_log_files_in_group | 2 |
+---------------------------+-------+
1 row in set, 1 warning (0.00 sec)
```
结果显示变量innodb_log_files_in_group值是2表示有2个重做日志文件。
需要注意的是变量innodb_log_files_in_group值的取值范围是1~4这四个文件分别用于记录不同的操作.
1. 用户创建表的插入操作;
1. 用户创建表的更新和删除操作;
1. 临时表的插入操作;
1. 临时表的更新和删除操作。
那么为什么在我的电脑上变量innodb_log_files_in_group值是2呢其实这是因为我只执行了对用户创建表的插入操作和更新删除操作所以只用到了2个文件。如果我还执行了临时表的插入和更新删除的操作那么这个变量的值就会变成4也就是会有4个重做日志文件了。
## 总结
这节课,我们学习了二进制日志、中继日志、回滚日志和重做日志。
1. 二进制日志:主要用于主从服务器之间的数据同步,以及服务器遇到故障时数据的无损失恢复。
1. 中继日志:就是主从服务器架构中,从服务器用来存放主服务器二进制日志内容的一个中间文件。从服务器通过读取中继日志的内容,来同步主服务器上的操作。
1. 回滚日志:用来存储事务中数据更新前的状态,以便回滚和保持其他事务的数据一致性。
1. 重做日志:是为了确保数值持久性、防止数据更新丢失的一种日志。
在这几种日志中,你一定要格外注意二进制日志的用法。有了它,我们就可以通过数据库的全量备份和二进制日志中保存的增量信息,完成数据库的无损失恢复。不过,我要提醒你的是,如果你遇到数据量大、数据库和数据表很多(比如分库分表的应用)的场景,用二进制日志进行数据恢复,是很有挑战性的,因为起止位置不容易管理。
在这种情况下,一个有效的解决办法是配置主从数据库服务器,甚至是一主多从的架构,把二进制日志文件的内容通过中继日志,同步到从数据库服务器中,这样就可以有效避免数据库故障导致的数据异常等问题。
## 思考题
下面是一段二进制日志中事件的内容:
```
mysql&gt; SHOW BINLOG EVENTS IN 'GJTECH-PC-bin.000013';
+----------------------+------+----------------+-----------+-------------+--------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+----------------------+------+----------------+-----------+-------------+--------------------------------------+
| GJTECH-PC-bin.000013 | 556 | Query | 1 | 627 | BEGIN |
| GJTECH-PC-bin.000013 | 627 | Table_map | 1 | 696 | table_id: 114 (demo.goodsmaster) |
| GJTECH-PC-bin.000013 | 696 | Delete_rows | 1 | 773 | table_id: 114 flags: STMT_END_F |
| GJTECH-PC-bin.000013 | 773 | Xid | 1 | 804 | COMMIT /* xid=253 */ |
| GJTECH-PC-bin.000013 | 804 | Anonymous_Gtid | 1 | 894 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| GJTECH-PC-bin.000013 | 894 | Query | 1 | 969 | BEGIN |
| GJTECH-PC-bin.000013 | 969 | Table_map | 1 | 1038 | table_id: 114 (demo.goodsmaster) |
| GJTECH-PC-bin.000013 | 1038 | Write_rows | 1 | 1094 | table_id: 114 flags: STMT_END_F |
| GJTECH-PC-bin.000013 | 1094 | Xid | 1 | 1125 | COMMIT /* xid=259 */ |
```
观察一下其中包括了哪几个SQL数据操作呢为了从二进制日志中恢复这些操作我们应该如何设置起始位置和截止位置呢
欢迎在留言区写下你的思考和答案,我们一起交流讨论。如果你觉得今天的内容对你有所帮助,也欢迎你分享给你的朋友或同事,我们下节课见。