CategoryResourceRepost/极客时间专栏/MySQL 必知必会/进阶篇/21 | 数据备份:异常情况下,如何确保数据安全?.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

467 lines
19 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="21 | 数据备份:异常情况下,如何确保数据安全?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/aa/38/aa398d679f7139c51800898e1886a238.mp3"></audio>
你好,我是朱晓峰。今天,我来和你聊一聊数据备份。
数据备份,对咱们技术人员来说十分重要。当成千上万的用户,每天使用我们开发的应用做着他们的日常工作的时候,数据的安全性就不光是你一个人的事了。要是有一天,突然发生了某种意想不到的情况,导致数据库服务器上的数据全部丢失,所有使用这个应用的人都会受到严重影响。
所以,我们必须“未雨绸缪”,及时把数据备份到安全的地方。这样,当突发的异常来临时,我们就能把数据及时恢复回来,就不会造成太大损失。
MySQL的数据备份有2种一种是物理备份通过把数据文件复制出来达到备份的目的另外一种是逻辑备份通过把描述数据库结构和内容的信息保存起来达到备份的目的。逻辑备份这种方式是免费的广泛得到使用而物理备份的方式需要收费用得比较少。所以这节课我重点和你聊聊逻辑备份。
我还会给你介绍一下MySQL中的数据备份工具mysqldump、数据恢复的命令行客户端工具mysql以及数据表中数据导出到文件和从文件导入的SQL语句帮助你提高你所开发的应用中的数据安全性。
## 如何进行数据备份?
首先我们来学习下用于数据备份的工具mysqldump。它总共有三种模式
1. 备份数据库中的表;
1. 备份整个数据库;
1. 备份整个数据库服务器。
接下来我就来介绍下这3种备份的具体方法。
### 如何备份数据库中的表?
mysqldump备份数据库中的表的语法结构是
```
mysqldump -h 服务器 -u 用户 -p 密码 数据库名称 [表名称 … ] &gt; 备份文件名称
```
我简单解释一下这里的核心内容。
- “-h”后面跟的服务器名称如果省略默认是本机“localhost”。
- “-u”后面跟的是用户名。
- “-p”后面跟的是密码如果省略执行的时候系统会提示录入密码。
我举个小例子,带你看看怎么使用这个工具。
假设数据库demo中有2个表分别是商品信息表demo.goodsmaster和会员表demo.membermaster
商品信息表:
<img src="https://static001.geekbang.org/resource/image/9c/45/9cd746385988ee32d8813ffbb12ed645.jpeg" alt="">
会员表:
<img src="https://static001.geekbang.org/resource/image/bd/87/bd0e205b0893773944275861ae9b6e87.jpeg" alt="">
现在我需要把数据库demo备份到文件中就可以用下面的代码实现
```
H:\&gt;mysqldump -u root -p demo goodsmaster membermaster &gt; test.sql
Enter password: *****
```
这个指令的意思就是备份本机数据库服务器上demo数据库中的商品信息表和会员信息表的所有信息。
**备份文件是以文本格式保存的**,我们可以用记事本打开,看一下备份的内容:
```
-- MySQL dump 10.13 Distrib 8.0.23, for Win64 (x86_64)
--
-- Host: localhost Database: demo -- 表示从本地进行备份数据库是demo
-- ------------------------------------------------------
-- Server version 8.0.23
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `goodsmaster` -- 商品信息表的结构
--
DROP TABLE IF EXISTS `goodsmaster`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `goodsmaster` (
`itemnumber` int NOT NULL,
`barcode` text,
`goodsname` text,
`specification` text,
`unit` text,
`salesprice` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`itemnumber`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `goodsmaster` -- 商品信息表的内容
--
LOCK TABLES `goodsmaster` WRITE;
/*!40000 ALTER TABLE `goodsmaster` DISABLE KEYS */;
INSERT INTO `goodsmaster` VALUES (1,'0001','书','16开','本',89.00),(2,'0002','笔','10支装','包',5.00),(3,'0003','橡皮',NULL,'个',3.00);
/*!40000 ALTER TABLE `goodsmaster` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `membermaster` -- 会员表的结构
--
DROP TABLE IF EXISTS `membermaster`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `membermaster` (
`id` int NOT NULL AUTO_INCREMENT,
`cardno` char(8) NOT NULL,
`membername` text,
`memberphone` text,
`memberpid` text,
`memberaddress` text,
`sex` text,
`birthday` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `membermaster` -- 会员表的内容
--
LOCK TABLES `membermaster` WRITE;
/*!40000 ALTER TABLE `membermaster` DISABLE KEYS */;
INSERT INTO `membermaster` VALUES ('10000001','张三','13812345678','110123200001017890','北京','男','2000-01-01 00:00:00',1),('10000002','李四','13512345678','123123199001012356','上海','女','1990-01-01 00:00:00',2);
/*!40000 ALTER TABLE `membermaster` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2021-04-11 10:43:04
```
从这个文件中我们可以看到它相当于一个SQL执行脚本里面包括了创建商品信息表和会员表的SQL语句以及把表里的数据插入到这两个表中的SQL语句。这样一来商品信息表和会员信息表的结构信息和全部数据信息就都备份出来了。
下面我来介绍一下备份整个数据库的方法。
### 如何备份数据库?
mysqldump备份数据库的语法结构是
```
mysqldump -h 服务器 -u 用户 -p 密码 --databases 数据库名称 … &gt; 备份文件名
```
举个小例子假设我现在需要对本机的数据库服务器中的2个数据库demo和demo1进行备份就可以用下面的指令
```
H:\&gt;mysqldump -u root -p --databases demo demo1 &gt; test1.sql
Enter password: *****
```
现在,我们来查看一下备份文件的内容:
```
-- MySQL dump 10.13 Distrib 8.0.23, for Win64 (x86_64)
--
-- Host: localhost Database: demo
-- ------------------------------------------------------
-- Server version 8.0.23
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Current Database: `demo` -- 备份数据库demo
--
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `demo` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `demo`; -- 备份数据库中的表
--
-- Table structure for table `dailystatistics`
--
DROP TABLE IF EXISTS `dailystatistics`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `dailystatistics` (
`id` int NOT NULL AUTO_INCREMENT,
`itemnumber` int DEFAULT NULL,
`quantity` decimal(10,3) DEFAULT NULL,
`actualvalue` decimal(10,2) DEFAULT NULL,
`cost` decimal(10,2) DEFAULT NULL,
`profit` decimal(10,2) DEFAULT NULL,
`profitratio` decimal(10,4) DEFAULT NULL,
`salesdate` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_dailystatistic_salesdate` (`salesdate`),
KEY `index_dailystatistic_itemnumber` (`itemnumber`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `dailystatistics`
--
LOCK TABLES `dailystatistics` WRITE;
/*!40000 ALTER TABLE `dailystatistics` DISABLE KEYS */;
INSERT INTO `dailystatistics` VALUES (15,1,3.000,267.00,100.50,166.50,0.6236,'2020-12-01 00:00:00'),(16,2,2.000,10.00,7.00,3.00,0.3000,'2020-12-01 00:00:00');
/*!40000 ALTER TABLE `dailystatistics` ENABLE KEYS */;
UNLOCK TABLES;
-- 这里省略了其他表的备份语句
--
-- Current Database: `demo1` -- 备份数据库demo1
--
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `demo1` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `demo1`;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2021-04-11 11:02:09
```
可以看到这个文件里面包含了创建数据库demo和demo1的SQL语句以及创建数据库中所有表、插入所有表中原有数据的SQL语句。
### 如何备份整个数据库服务器?
mysqldump备份整个数据库服务器的语法结构是
```
mysqldump -h 服务器 -u 用户 -p 密码 --all-databases &gt; 备份文件名
```
举个小例子假设我要把本机上整个MySQL服务器备份下来可以用下面的代码
```
H:\&gt;mysqldump -u root -p --all-databases &gt; test2.sql
Enter password: *****
```
这个指令表示备份本机上运行的MySQL数据库服务器的全部内容包含系统数据库和用户创建的数据库中的库结构信息、表结构信息和表里的数据。这种备份方式会把系统数据库也全部备份出来而且消耗的资源也比较多一般来说没有必要我就不展开细说了。
备份文件有了,如何用它进行数据恢复呢?下面我就来给你介绍下具体的方法。
## 如何进行数据恢复?
mysqldump的备份文件包含了创建数据库、数据表以及插入数据表里原有数据的SQL语句我们可以直接运行这些SQL语句来进行数据恢复。
数据恢复的方法主要有2种
- 使用“mysql”命令行客户端工具进行数据恢复
- 使用“SOURCE”语句进行数据恢复。
使用“mysql”命令行客户端工具进行数据恢复的命令如下
```
H:\&gt;mysql -u root -p demo &lt; test.sql
Enter password: *****
```
我来简单介绍下这个数据恢复命令。
- mysql是一个命令行客户端工具可以与MySQL服务器之间进行连接执行SQL语句。
- “-u”后面跟的是用户。
- “-p”后面跟的是密码。
在这个命令里面我指定了数据库因为备份文件test.sql里面只有数据表的备份信息需要指定恢复到哪个数据库中。如果使用的备份文件备份的是数据库的信息比如test1.sql或者是整个MySQL数据库服务器的信息比如test2.sql则不需要指定数据库。
第二种数据恢复的方法是使用“SOURCE”语句恢复数据语法结构如下
```
SOURCE 备份文件名
```
举个小例子,刚才我们对商品信息表和会员信息表进行了备份,现在想用备份的文件进行恢复,就可以用下面的语句:
```
mysql&gt; USE demo;
Database changed
mysql&gt; SOURCE H:\\test.sql
Query OK, 0 rows affected (0.00 sec)
```
注意这里需要先用“USE”语句把当前的数据库变更为demo这样商品信息表和会员表才能恢复到正确的数据库里面。否则可能会恢复错误。
除此之外你还可以通过这种方式用整个数据库的备份文件把数据库恢复回来甚至是用整个数据库服务器的备份文件恢复整个MySQL服务器。
到这里我们就掌握了备份和恢复整个数据库服务器、数据库和数据库中的表的方法。不过有的时候我们只关心表里的数据本身希望能够把表里的数据按照一定的格式保存下来。这个时候mysqldump就不够用了。所以接下来我再给你介绍下MySQL数据导出和导入的方法。
## 如何导出和导入表里的数据?
先来学习下怎么把一个表的数据按照一定的格式,导出成一个文件。
### SELECT语句导出数据
使用“SELECT … INTO OUTFILE”语句导出数据表的语法结构是
```
SELECT 字段列表 INTO OUTFILE 文件名称
FIELDS TERMINATED BY 字符
LINES TERMINATED BY 字符
FROM 表名;
```
我来解释下这段代码。
- INTO OUTFILE 文件名称,表示查询的结果保存到文件名称指定的文件中;
- FIELDS TERMINATED BY 字符,表示列之间的分隔符是“字符”;
- LINES TERMINATED BY 字符,表示行之间的分隔符是“字符”。
举个小例子假设我们要把商品信息表导出到文件H:\goodsmaster.txt中该如何实现呢按照我刚刚介绍的语法结构来尝试一下
```
mysql&gt; SELECT * INTO OUTFILE 'H:\goodsmaster.txt'
-&gt; FIELDS TERMINATED BY ','
-&gt; LINES TERMINATED BY '\n'
-&gt; FROM demo.goodsmaster;
ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement
```
结果,系统提示错误。其实,这是因为**服务器的“secure-file-priv”参数选项不允许把文件写入到H:\goodsmaster.txt中**。那怎么解决这个问题呢?
这个时候我们可以通过MySQL的配置文件my.ini来查看一下“secure-file-priv”参数的设定并且按照这个参数设定的要求准备导入文件。
打开C:\ProgramData\MySQL\MySQL Server 8.0\my.ini找到“secure-file-priv”参数设定如下所示
```
# Secure File Priv.
secure-file-priv=&quot;C:/ProgramData/MySQL/MySQL Server 8.0/Uploads&quot;
```
这个意思是说只能把数据导出到“C:/ProgramData/MySQL/MySQL Server 8.0/Uploads”这个文件夹中所以如果我们把数据导出到H:\goodsmaster.txt中就违反了系统参数的设定导致发生错误。
现在我们来修改一下数据导出的SQL语句把导出文件的路径改到系统要求的文件目录看看结果如何
```
mysql&gt; SELECT * INTO OUTFILE 'C:/ProgramData/MySQL/MySQL Server 8.0/Uploads/goodsmaster.txt'
-&gt; FIELDS TERMINATED BY ','
-&gt; LINES TERMINATED BY '\n'
-&gt; FROM demo.goodsmaster;
Query OK, 3 rows affected (0.00 sec)
```
结果显示,执行成功了。下面我们来看一下结果文件的内容:
```
1,0001,书,16开,本,89.00
2,0002,笔,10支装,包,5.00
3,0003,橡皮,\N,个,3.00
```
很显然,这很符合我们希望的导出格式:行与行之间用回车“\n”分隔列与列之间用逗号“,”分隔。
到这里,我们就知道怎么把数据表中的数据按照一定的格式导出到文件了。那在实际工作中,我们还经常需要把一定格式的数据从文件中导入到数据表中。
“LOAD DATA”语句就是MySQL提供的一种快速数据读入的方法在实际工作中常用于大量数据的导入效率极高。
### 使用“LOAD DATA”语句导入数据
“LOAD DATA”是与“SELECT … INTO OUTFILE”相对应的数据导入语句。语句结构是
```
LOAD DATA INFILE 文件名
INTO TABLE 表名
FIELDS TERMINATED BY 字符
LINES TERMINATED BY 字符;
```
我举个小例子来演示一下“LOAD DATA”语句是如何工作的。
还是以我们刚才导出的那个文件goodsmaster.txt为例现在我们把这个文件内的数据导入到商品信息表demo.goodsmaster中去。
为了演示方便我会先把demo.goodsmaster中的数据先删除然后使用“LOAD DATA”语句把刚才的导出文件goodsmaster.txt的内容导入进来再与删除之前的数据进行对比来验证“LOAD DATA”语句的执行效果。
首先,我们把商品信息表中的数据删除:
```
mysql&gt; DELETE FROM demo.goodsmaster
-&gt; WHERE itemnumber&gt;0;
Query OK, 3 rows affected (0.03 sec)
```
然后我们尝试把文件goodsmaster.txt中的数据导入进来
```
mysql&gt; LOAD DATA INFILE 'C:/ProgramData/MySQL/MySQL Server 8.0/Uploads/goodsmaster.txt'
-&gt; INTO TABLE demo.goodsmaster
-&gt; FIELDS TERMINATED BY ','
-&gt; LINES TERMINATED BY '\n';
Query OK, 3 rows affected (0.02 sec)
Records: 3 Deleted: 0 Skipped: 0 Warnings: 0
```
结果显示,导入成功了。我们再查看一下数据表中的内容:
```
mysql&gt; SELECT * FROM demo.goodsmaster;
+------------+---------+-----------+---------------+------+------------+
| itemnumber | barcode | goodsname | specification | unit | salesprice |
+------------+---------+-----------+---------------+------+------------+
| 1 | 0001 | 书 | 16开 | 本 | 89.00 |
| 2 | 0002 | 笔 | 10支装 | 包 | 5.00 |
| 3 | 0003 | 橡皮 | NULL | 个 | 3.00 |
+------------+---------+-----------+---------------+------+------------+
3 rows in set (0.00 sec)
```
结果显示与我们删除之前的数据完全一致。这说明“LOAD DATA”语句成功导入了数据文件goodsmaster.txt中的数据。
## 总结
今天我们重点学习了数据备份包括数据备份的工具mysqldump以及用命令行客户端工具“mysql”和SQL语句“SOURCE”进行数据恢复的方法。同时我还给你介绍了用于导出数据表中数据的语句“SELECT … INTO OUTFILE”和导入的语句“LOAD DATA”。这些都是你在备份数据时必不可少的对确保数据的安全性至关重要。
最后提醒你一点“LOAD DATA”是很好用的工具因为它的数据导入速度是非常惊人的。一个400万条数据的文件用“LOAD DATA”语句只需要几分钟就可以完成而其他的方法比如使用Workbench来导入数据就需要花费好几个小时。
## 思考题
这节课我介绍了数据导出语句“SELECT … INTO OUTFILE”并且在第一次数据导出时遇到了一个系统参数“secure-file-priv”设定的目录与导出文件目录不一致从而导致导出失败的问题。最后我通过修改导出文件的文件夹解决了这个问题。
我想请你思考一下如果还是想把导出文件保存到H:\目录下,有没有办法实现呢?
欢迎在留言区写下你的思考和答案,我们一起交流讨论。如果你觉得今天的内容对你有所帮助,也欢迎你分享给你的朋友或同事,我们下节课见。