[toc]
第17章_其他数据库日志
千万不要小看日志。很多看似奇怪的问题,答案往往就藏在日志里。很多情况下,只有通过查看日志才能发现问题的原因,真正解决问题。所以,一定要学会查看日志,养成检查日志的习惯,对提升你的数据库应用开发能力至关重要。
MySQL8.0 官网日志地址:“ https://dev.mysql.com/doc/refman/8.0/en/server-logs.html ”
1. MySQL支持的日志
1.1 日志类型
MySQL有不同类型的日志文件,用来存储不同类型的日志,分为 二进制日志
、 错误日志
、 通用查询日志
和 慢查询日志
,这也是常用的4种。MySQL8又新增两种支持的日志:中继日志和数据定义语句日志。使用这些日志文件,可以查看MySQL内部发生的事情。
这6类日志分别为:
- 慢查询日志:记录所有执行时间超过long_query_time的所有查询,方便我们对查询进行优化。
- 通用查询日志:记录所有连接的起始时间和终止时间,以及连接发送给数据库服务器的所有指令, 对我们复原操作的实际场景、发现问题,甚至是对数据库操作的审计都有很大的帮助。
- 错误日志:记录MySQL服务的启动、运行或停止MySQL服务时出现的问题,方便我们了解服务器的状态,从而对服务器进行维护。
- 二进制日志:记录所有更改数据的语句,可以用于主从服务器之间的数据同步,以及服务器遇到故障时数据的无损失恢复。
- 中继日志:用于主从服务器架构中,从服务器用来存放主服务器二进制日志内容的一个中间文件。从服务器通过读取中继日志的内容,来同步主服务器上的操作。
- 数据定义语句日志:记录数据定义语句执行的元数据操作。
除二进制日志外,其他日志都是 文本文件
。默认情况下,所有日志创建于 MySQL数据目录
中。
1.2 日志的弊端
- 日志功能会
降低MySQL数据库的性能
。例如,在查询非常频繁的MySQL数据库系统中,如果开启了通用查询日志和慢查询日志,MySQL数据库会花费很多时间记录日志。
- 日志会
占用大量的磁盘空间
。对于用户量非常大,操作非常频繁的数据库,日志文件需要的存储空间设置比数据库文件需要的存储空间还要大。
2. 慢查询日志(slow query log)
前面章节《第09章_性能分析工具的使用》已经详细讲述。
3. 通用查询日志(general query log)
通用查询日志用来 记录用户的所有操作
,包括启动和关闭MySQL服务、所有用户的连接开始时间和截止 时间、发给 MySQL 数据库服务器的所有 SQL 指令等。当我们的数据发生异常时,查看通用查询日志, 还原操作时的具体场景,可以帮助我们准确定位问题。
3.1 问题场景
3.2 查看当前状态
1
2
3
4
5
6
7
8
|
mysql> SHOW VARIABLES LIKE '%general%';
+------------------+------------------------------+
| Variable_name | Value |
+------------------+------------------------------+
| general_log | OFF | #通用查询日志处于关闭状态
| general_log_file | /var/lib/mysql/atguigu01.log | #通用查询日志文件的名称是atguigu01.log
+------------------+------------------------------+
2 rows in set (0.03 sec)
|
3.3 启动日志
方式1:永久性方式
修改my.cnf或者my.ini配置文件来设置。在[mysqld]组下加入log选项,并重启MySQL服务。格式如下:
1
2
3
|
[mysqld]
general_log=ON
general_log_file=[path[filename]] #日志文件所在目录路径,filename为日志文件
|
如果不指定目录和文件名,通用查询日志将默认存储在MySQL数据目录中的hostname.log文件中, hostname表示主机名。
方式2:临时性方式
1
|
SET GLOBAL general_log=on; # 开启通用查询日志
|
1
|
SET GLOBAL general_log_file=’path/filename’; # 设置日志文件保存位置
|
对应的,关闭操作SQL命令如下:
1
|
SET GLOBAL general_log=off; # 关闭通用查询日志
|
查看设置后情况:
1
|
SHOW VARIABLES LIKE 'general_log%';
|
3.4 查看日志
通用查询日志是以 文本文件
的形式存储在文件系统中的,可以使用 文本编辑器
直接打开日志文件。每台 MySQL服务器的通用查询日志内容是不同的。
- 在Windows操作系统中,使用文本文件查看器;
- 在Linux系统中,可以使用vi工具或者gedit工具查看;
- 在Mac OSX系统中,可以使用文本文件查看器或者vi等工具查看。
从 SHOW VARIABLES LIKE 'general_log%'
; 结果中可以看到通用查询日志的位置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/usr/sbin/mysqld, Version: 8.0.26 (MySQL Community Server - GPL). started with:
Tcp port: 3306 Unix socket: /var/lib/mysql/mysql.sock
Time Id Command Argument
2022-01-04T07:44:58.052890Z 10 Query SHOW VARIABLES LIKE '%general%'
2022-01-04T07:45:15.666672Z 10 Query SHOW VARIABLES LIKE 'general_log%'
2022-01-04T07:45:28.970765Z 10 Query select * from student
2022-01-04T07:47:38.706804Z 11 Connect root@localhost on using Socket
2022-01-04T07:47:38.707435Z 11 Query select @@version_comment limit 1
2022-01-04T07:48:21.384886Z 12 Connect root@172.16.210.1 on using TCP/IP
2022-01-04T07:48:21.385253Z 12 Query SET NAMES utf8
2022-01-04T07:48:21.385640Z 12 Query USE `atguigu12`
2022-01-04T07:48:21.386179Z 12 Query SHOW FULL TABLES WHERE Table_Type !=
'VIEW'
2022-01-04T07:48:23.901778Z 13 Connect root@172.16.210.1 on using TCP/IP
2022-01-04T07:48:23.902128Z 13 Query SET NAMES utf8
2022-01-04T07:48:23.905179Z 13 Query USE `atguigu`
2022-01-04T07:48:23.905825Z 13 Query SHOW FULL TABLES WHERE Table_Type !=
'VIEW'
2022-01-04T07:48:32.163833Z 14 Connect root@172.16.210.1 on using TCP/IP
2022-01-04T07:48:32.164451Z 14 Query SET NAMES utf8
2022-01-04T07:48:32.164840Z 14 Query USE `atguigu`
2022-01-04T07:48:40.006687Z 14 Query select * from account
|
在通用查询日志里面,我们可以清楚地看到,什么时候开启了新的客户端登陆数据库,登录之后做了什么 SQL 操作,针对的是哪个数据表等信息。
3.5 停止日志
方式1:永久性方式
修改 my.cnf
或者 my.ini
文件,把[mysqld]组下的 general_log
值设置为 OFF
或者把general_log一项 注释掉。修改保存后,再重启MySQL服务
,即可生效。
举例1:
1
2
|
[mysqld]
general_log=OFF
|
举例2:
1
2
|
[mysqld]
#general_log=ON
|
方式2:临时性方式
使用SET语句停止MySQL通用查询日志功能:
1
|
SET GLOBAL general_log=off;
|
查询通用日志功能:
1
|
SHOW VARIABLES LIKE 'general_log%';
|
3.6 删除\刷新日志
如果数据的使用非常频繁,那么通用查询日志会占用服务器非常大的磁盘空间。数据管理员可以删除很长时间之前的查询日志,以保证MySQL服务器上的硬盘空间。
手动删除文件
1
|
SHOW VARIABLES LIKE 'general_log%';
|
可以看出,通用查询日志的目录默认为MySQL数据目录。在该目录下手动删除通用查询日志 atguigu01.log
使用如下命令重新生成查询日志文件,具体命令如下。刷新MySQL数据目录,发现创建了新的日志文 件。前提一定要开启通用日志。
1
|
mysqladmin -uroot -p flush-logs
|
如果希望备份旧的通用查询日志,就必须先将旧的日志文件复制出来或者改名,然后执行上面的mysqladmin命令。正确流程如下:
1
2
3
|
cd mysql-data-directory # 输入自己的通用日志文件所在目录
mv mysql.general.log mysql.general.log.old # 指定旧的文件名 以及 新的文件名
mysqladmin -uroot -p flush-logs
|
4. 错误日志(error log)
4.1 启动日志
在MySQL数据库中,错误日志功能是 默认开启
的。而且,错误日志 无法被禁止
。
默认情况下,错误日志存储在MySQL数据库的数据文件夹下,名称默认为 mysqld.log
(Linux系统)或 hostname.err
(mac系统)。如果需要制定文件名,则需要在my.cnf或者my.ini中做如下配置:
1
2
|
[mysqld]
log-error=[path/[filename]] #path为日志文件所在的目录路径,filename为日志文件名
|
修改配置项后,需要重启MySQL服务以生效。
4.2 查看日志
MySQL错误日志是以文本文件形式存储的,可以使用文本编辑器直接查看。
查询错误日志的存储路径:
1
2
3
4
5
6
7
8
9
10
|
mysql> SHOW VARIABLES LIKE 'log_err%';
+----------------------------+----------------------------------------+
| Variable_name | Value |
+----------------------------+----------------------------------------+
| log_error | /var/log/mysqld.log |
| log_error_services | log_filter_internal; log_sink_internal |
| log_error_suppression_list | |
| log_error_verbosity | 2 |
+----------------------------+----------------------------------------+
4 rows in set (0.01 sec)
|
执行结果中可以看到错误日志文件是mysqld.log,位于MySQL默认的数据目录下。
4.3 删除\刷新日志
对于很久以前的错误日志,数据库管理员查看这些错误日志的可能性不大,可以将这些错误日志删除, 以保证MySQL服务器上的 硬盘空间
。MySQL的错误日志是以文本文件的形式存储在文件系统中的,可以 直接删除
。
补充操作:
1
|
install -omysql -gmysql -m0644 /dev/null /var/log/mysqld.log
|
4.4 MySQL 8.0 新特性
小结:
通常情况下,管理员不需要查看错误日志。但是,MySQL服务器发生异常时,管理员可以从错误日志中找到发生异常的时间、原因,然后根据这些信息来解决异常。
5. 二进制日志(bin log)
binlog可以说是MySQL中比较 重要
的日志了,在日常开发及运维过程中,经常会遇到。
binlog即binary log,二进制日志文件,也叫作变更日志(update log)。它记录了数据库所有执行的 DDL
和 DML
等数据库更新事件的语句,但是不包含没有修改任何数据的语句(如数据查询语句select、 show等)。
它以事件形式
记录并保存在二进制文件
中。通过这些信息,我们可以再现数据更新操作的全过程。
如果想要记录所有语句(例如,为了识别有问题的查询),需要使用通用查询日志。
binlog主要应用场景:
5.1 查看默认情况
查看记录二进制日志是否开启:在MySQL8中默认情况下,二进制文件是开启的。
1
2
3
4
5
6
7
8
9
10
11
12
|
mysql> show variables like '%log_bin%';
+---------------------------------+----------------------------------+
| Variable_name | Value |
+---------------------------------+----------------------------------+
| log_bin | ON |
| log_bin_basename | /var/lib/mysql/binlog |
| log_bin_index | /var/lib/mysql/binlog.index |
| log_bin_trust_function_creators | OFF |
| log_bin_use_v1_row_events | OFF |
| sql_log_bin | ON |
+---------------------------------+----------------------------------+
6 rows in set (0.00 sec)
|
5.2 日志参数设置
方式1:永久性方式
修改MySQL的 my.cnf
或 my.ini
文件可以设置二进制日志的相关参数:
1
2
3
4
5
|
[mysqld]
#启用二进制日志
log-bin=atguigu-bin
binlog_expire_logs_seconds=600
max_binlog_size=100M
|
重新启动MySQL服务,查询二进制日志的信息,执行结果:
1
2
3
4
5
6
7
8
9
10
11
12
|
mysql> show variables like '%log_bin%';
+---------------------------------+----------------------------------+
| Variable_name | Value |
+---------------------------------+----------------------------------+
| log_bin | ON |
| log_bin_basename | /var/lib/mysql/atguigu-bin |
| log_bin_index | /var/lib/mysql/atguigu-bin.index |
| log_bin_trust_function_creators | OFF |
| log_bin_use_v1_row_events | OFF |
| sql_log_bin | ON |
+---------------------------------+----------------------------------+
6 rows in set (0.00 sec)
|
设置带文件夹的bin-log日志存放目录
如果想改变日志文件的目录和名称,可以对my.cnf或my.ini中的log_bin参数修改如下:
1
2
|
[mysqld]
log-bin="/var/lib/mysql/binlog/atguigu-bin"
|
注意:新建的文件夹需要使用mysql用户,使用下面的命令即可。
1
|
chown -R -v mysql:mysql binlog
|
方式2:临时性方式
如果不希望通过修改配置文件并重启的方式设置二进制日志的话,还可以使用如下指令,需要注意的是 在mysql8中只有 会话级别
的设置,没有了global级别的设置。
1
2
3
4
5
6
7
8
|
# global 级别
mysql> set global sql_log_bin=0;
ERROR 1228 (HY000): Variable 'sql_log_bin' is a SESSION variable and can`t be used
with SET GLOBAL
# session级别
mysql> SET sql_log_bin=0;
Query OK, 0 rows affected (0.01 秒)
|
5.3 查看日志
当MySQL创建二进制日志文件时,先创建一个以“filename”为名称、以“.index”为后缀的文件,再创建一个以“filename”为名称、以“.000001”为后缀的文件。
MySQL服务 重新启动一次
,以“.000001”为后缀的文件就会增加一个,并且后缀名按1递增。即日志文件的个数与MySQL服务启动的次数相同;如果日志长度超过了 max_binlog_size
的上限(默认是1GB),就会创建一个新的日志文件。
查看当前的二进制日志文件列表及大小。指令如下:
1
2
3
4
5
6
7
|
mysql> SHOW BINARY LOGS;
+--------------------+-----------+-----------+
| Log_name | File_size | Encrypted |
+--------------------+-----------+-----------+
| atguigu-bin.000001 | 156 | No |
+--------------------+-----------+-----------+
1 行于数据集 (0.02 秒)
|
所有对数据库的修改都会记录在binlog中。但binlog是二进制文件,无法直接查看,想要更直观的观测它就要借助mysqlbinlog
命令工具了。指令如下:在查看执行,先执行一条SQL语句,如下
1
|
update student set name='张三_back' where id=1;
|
开始查看binlog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
mysqlbinlog -v "/var/lib/mysql/binlog/atguigu-bin.000002"
#220105 9:16:37 server id 1 end_log_pos 324 CRC32 0x6b31978b Query thread_id=10
exec_time=0 error_code=0
SET TIMESTAMP=1641345397/*!*/;
SET @@session.pseudo_thread_id=10/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0,
@@session.unique_checks=1, @@session.autocommit=1/*!*/;
SET @@session.sql_mode=1168113696/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8mb3 *//*!*/;
SET
@@session.character_set_client=33,@@session.collation_connection=33,@@session.collatio
n_server=255/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
/*!80011 SET @@session.default_collation_for_utf8mb4=255*//*!*/;
BEGIN
/*!*/;
# at 324
#220105 9:16:37 server id 1 end_log_pos 391 CRC32 0x74f89890 Table_map:
`atguigu14`.`student` mapped to number 85
# at 391
#220105 9:16:37 server id 1 end_log_pos 470 CRC32 0xc9920491 Update_rows: table id
85 flags: STMT_END_F
BINLOG '
dfHUYRMBAAAAQwAAAIcBAAAAAFUAAAAAAAEACWF0Z3VpZ3UxNAAHc3R1ZGVudAADAw8PBDwAHgAG
AQEAAgEhkJj4dA==
dfHUYR8BAAAATwAAANYBAAAAAFUAAAAAAAEAAgAD//8AAQAAAAblvKDkuIkG5LiA54+tAAEAAAAL
5byg5LiJX2JhY2sG5LiA54+tkQSSyQ==
'/*!*/;
### UPDATE `atguigu`.`student`
### WHERE
### @1=1
### @2='张三'
### @3='一班'
### SET
### @1=1
### @2='张三_back'
### @3='一班'
# at 470
#220105 9:16:37 server id 1 end_log_pos 501 CRC32 0xca01d30f Xid = 15
COMMIT/*!*/;
|
前面的命令同时显示binlog格式的语句,使用如下命令不显示它
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
mysqlbinlog -v --base64-output=DECODE-ROWS "/var/lib/mysql/binlog/atguigu-bin.000002"
#220105 9:16:37 server id 1 end_log_pos 324 CRC32 0x6b31978b Query thread_id=10
exec_time=0 error_code=0
SET TIMESTAMP=1641345397/*!*/;
SET @@session.pseudo_thread_id=10/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0,
@@session.unique_checks=1, @@session.autocommit=1/*!*/;
SET @@session.sql_mode=1168113696/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8mb3 *//*!*/;
SET
@@session.character_set_client=33,@@session.collation_connection=33,@@session.collatio
n_server=255/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
/*!80011 SET @@session.default_collation_for_utf8mb4=255*//*!*/;
BEGIN
/*!*/;
# at 324
#220105 9:16:37 server id 1 end_log_pos 391 CRC32 0x74f89890 Table_map:
`atguigu14`.`student` mapped to number 85
# at 391
#220105 9:16:37 server id 1 end_log_pos 470 CRC32 0xc9920491 Update_rows: table id
85 flags: STMT_END_F
### UPDATE `atguigu14`.`student`
### WHERE
### @1=1
### @2='张三'
### @3='一班'
### SET
### @1=1
### @2='张三_back'
### @3='一班'
# at 470
#220105 9:16:37 server id 1 end_log_pos 501 CRC32 0xca01d30f Xid = 15
|
关于mysqlbinlog工具的使用技巧还有很多,例如只解析对某个库的操作或者某个时间段内的操作等。简单分享几个常用的语句,更多操作可以参考官方文档。
1
2
3
4
5
6
7
8
|
# 可查看参数帮助
mysqlbinlog --no-defaults --help
# 查看最后100行
mysqlbinlog --no-defaults --base64-output=decode-rows -vv atguigu-bin.000002 |tail
-100
# 根据position查找
mysqlbinlog --no-defaults --base64-output=decode-rows -vv atguigu-bin.000002 |grep -A
20 '4939002'
|
上面这种办法读取出binlog日志的全文内容比较多,不容易分辨查看到pos点信息,下面介绍一种更为方便的查询命令:
1
|
mysql> show binlog events [IN 'log_name'] [FROM pos] [LIMIT [offset,] row_count];
|
IN 'log_name'
:指定要查询的binlog文件名(不指定就是第一个binlog文件)
FROM pos
:指定从哪个pos起始点开始查起(不指定就是从整个文件首个pos点开始算)
LIMIT [offset]
:偏移量(不指定就是0)
row_count
:查询总条数(不指定就是所有行)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
mysql> show binlog events in 'atguigu-bin.000002';
+--------------------+-----+----------------+-----------+-------------+--------------------------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+--------------------+-----+----------------+-----------+-------------+--------------------------------------------------------+
| atguigu-bin.000002 | 4 | Format_desc | 1 | 125 | Server ver: 8.0.26, Binlog ver: 4 |
| atguigu-bin.000002 | 125 | Previous_gtids | 1 | 156 | |
| atguigu-bin.000002 | 156 | Anonymous_Gtid | 1 | 235 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| atguigu-bin.000002 | 235 | Query | 1 | 324 | BEGIN |
| atguigu-bin.000002 | 324 | Table_map | 1 | 391 | table_id: 85(atguigu14.student) |
| atguigu-bin.000002 | 391 | Update_rows | 1 | 470 | table_id: 85flags: STMT_END_F |
| atguigu-bin.000002 | 470 | Xid | 1 | 501 | COMMIT /*xid=15 */ |
| atguigu-bin.000002 | 501 | Anonymous_Gtid | 1 | 578 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| atguigu-bin.000002 | 578 | Query | 1 | 721 | use `atguigu14`; create table test(id int, title varchar(100)) /* xid=19 */ |
| atguigu-bin.000002 | 721 | Anonymous_Gtid | 1 | 800 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| atguigu-bin.000002 | 800 | Query | 1 | 880 | BEGIN |
| atguigu-bin.000002 | 880 | Table_map | 1 | 943 | table_id: 89(atguigu14.test) |
| atguigu-bin.000002 | 943 | Write_rows | 1 | 992 | table_id: 89 flags: STMT_END_F |
| atguigu-bin.000002 | 992 | Xid | 1 | 1023 | COMMIT /*xid=21 */ |
+--------------------+-----+----------------+-----------+-------------+--------------------------------------------------------+
14 行于数据集 (0.02 秒)
|
上面我们讲了这么多都是基于binlog的默认格式,binlog格式查看
1
2
3
4
5
6
7
|
mysql> show variables like 'binlog_format';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW |
+---------------+-------+
1 行于数据集 (0.02 秒)
|
除此之外,binlog还有2种格式,分别是Statement
和Mixed
-
Statement
每一条会修改数据的sql都会记录在binlog中。
优点:不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。
-
Row
5.1.5版本的MySQL才开始支持row level 的复制,它不记录sql语句上下文相关信息,仅保存哪条记录被修改。
优点:row level 的日志内容会非常清楚的记录下每一行数据修改的细节。而且不会出现某些特定情况下 的存储过程,或function,以及trigger的调用和触发无法被正确复制的问题。
-
Mixed
从5.1.8版本开始,MySQL提供了Mixed格式,实际上就是Statement与Row的结合。
详细情况,下章讲解。
5.4 使用日志恢复数据
如果MySQL服务器启用了二进制日志,在数据库出现意外丢失数据时,可以使用MySQLbinlog工具从指定的时间点开始(例如,最后一次备份)直到现在或另一个指定的时间点的日志中回复数据。
mysqlbinlog恢复数据的语法如下:
1
|
mysqlbinlog [option] filename|mysql –uuser -ppass;
|
这个命令可以这样理解:使用mysqlbinlog命令来读取filename中的内容,然后使用mysql命令将这些内容恢复到数据库中。
注意:使用mysqlbinlog命令进行恢复操作时,必须是编号小的先恢复,例如atguigu-bin.000001必须在atguigu-bin.000002之前恢复。
详见p189,由于翻页过快,这部分没办法记录。
5.5 删除二进制日志
MySQL的二进制文件可以配置自动删除,同时MySQL也提供了安全的手动删除二进制文件的方法。 PURGE MASTER LOGS
只删除指定部分的二进制日志文件, RESET MASTER
删除所有的二进制日志文 件。具体如下:
1. PURGE MASTER LOGS:删除指定日志文件
PURGE MASTER LOGS语法如下:
1
2
|
PURGE {MASTER | BINARY} LOGS TO ‘指定日志文件名’
PURGE {MASTER | BINARY} LOGS BEFORE ‘指定日期’
|
2. RESET MASTER: 删除所有二进制日志文件
5.6 其它场景
二进制日志可以通过数据库的 全量备份
和二进制日志中保存的 增量信息
,完成数据库的 无损失恢复
。 但是,如果遇到数据量大、数据库和数据表很多(比如分库分表的应用)的场景,用二进制日志进行数据恢复,是很有挑战性的,因为起止位置不容易管理。
在这种情况下,一个有效的解决办法是配置主从数据库服务器
,甚至是一主多从
的架构,把二进制日志文件的内容通过中继日志,同步到从数据库服务器中,这样就可以有效避免数据库故障导致的数据异常等问题。
6. 再谈二进制日志(binlog)
6.1 写入机制
binlog的写入时机也非常简单,事务执行过程中,先把日志写到 binlog cache
,事务提交的时候,再把binlog cache写到binlog文件中。因为一个事务的binlog不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为binlog cache。(保证了事务的原子性)
我们可以通过binlog_cache_size
参数控制单个线程 binlog cache
大小,如果存储内容超过了这个参数,就要暂存到磁盘(Swap)。binlog日志刷盘流程如下:
- 上图的write,是指把日志写入到文件系统的page cache,并没有把数据持久化到磁盘,所以速度比较快
- 上图的fsync,才是将数据持久化到磁盘的操作
write和fsync的时机,可以由参数 sync_binlog
控制,默认是 0
。
为0的时候,表示每次提交事务都只 write,由系统自行判断什么时候执行fsync。虽然性能得到提升,但是机器宕机,page cache里面的 binglog 会丢失。如下图:
为了安全起见,可以设置为 1
,表示每次提交事务都会执行fsync,就如同redo log 刷盘流程一样。
最后还有一种折中方式,可以设置为N(N>1),表示每次提交事务都write,但累积N个事务后才fsync。
在出现IO瓶颈的场景里,将sync_binlog设置成一个比较大的值,可以提升性能。同样的,如果机器宕机,会丢失最近N个事务的binlog日志。
6.2 binlog与redo log对比
- redo log 它是
物理日志
,记录内容是“在某个数据页上做了什么修改”,属于 InnoDB 存储引擎层产生的。
- 而 binlog 是
逻辑日志
,记录内容是语句的原始逻辑,类似于“给 ID=2 这一行的 c 字段加 1”,属于 MySQL Server 层。
- 虽然它们都属于持久化的保证,但是侧重点不同。
- redo log让InnoDB存储引擎拥有了崩溃恢复能力。
- binlog保证了MySQL集群架构的数据一致性。
6.3 两阶段提交
在执行更新语句过程,会记录redo log与binlog两块日志,以基本的事务为单位,redo log在事务执行过程中可以不断写入,而binlog只有在提交事务时才会从binlog cache写入,所以redo log与binlog的 写入时机
不一样。
redo log与binlog两份日志之间的逻辑不一致,会出现什么问题?
以update语句为例,假设id=2
的记录,字段c
值是0
,把字段c值更新为1
,SQL语句为update T set c = 1 where id = 2
。
假设执行过程中写完redo log日志后,binlog日志写期间发生了异常,会出现什么情况呢?
由于binlog没写完就异常,这时候binlog里面没有对应的修改记录。因此,之后用binlog日志恢复数据或主从数据同步时,就会少这一次更新,恢复出来的这一行c值为0,而原库因为redo log日志恢复,这一行c的值是1,最终数据不一致。
为了解决两份日志之间的逻辑一致问题,InnoDB存储引擎使用两阶段提交方案。原理很简单,将redo log的写入拆成了两个步骤prepare和commit,这就是两阶段提交。
使用两阶段提交后,写入binlog时发生异常也不会有影响,因为MySQL根据redo log日志恢复数据时,发现redo log还处于prepare阶段,并且没有对应binlog日志,就会回滚该事务。
另一个场景,redo log设置commit阶段发生异常,那会不会回滚事务呢?
并不会回滚事务,它会执行上图框住的逻辑,虽然redo log是处于prepare阶段,但是能通过事务id找到对应的binlog日志,所以MySQL认为是完整的,就会提交事务恢复数据。
7. 中继日志(relay log)
7.1 介绍
中继日志只在主从服务器架构的从服务器上存在。从服务器为了与主服务器保持一致,要从主服务器读取二进制日志的内容,并且把读取到的信息写入 本地的日志文件
中,这个从服务器本地的日志文件就叫 中继日志
。然后,从服务器读取中继日志,并根据中继日志的内容对从服务器的数据进行更新,完成主从服务器的数据同步 。
搭建好主从服务器之后,中继日志默认会保存在从服务器的数据目录下。
文件名的格式是: 从服务器名 -relay-bin.序号
。中继日志还有一个索引文件:从服务器名 -relaybin.index
,用来定位当前正在使用的中继日志。
7.2 查看中继日志
中继日志与二进制日志的格式相同,可以用 mysqlbinlog
工具进行查看。下面是中继日志的一个片段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
SET TIMESTAMP=1618558728/*!*/;
BEGIN
/*!*/;
# at 950
#210416 15:38:48 server id 1 end_log_pos 832 CRC32 0xcc16d651 Table_map:
`atguigu`.`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”)对表 atguigu.test 进行了 2 步操作:
1
2
|
定位到表 atguigu.test 编号是 91 的记录,日志位置是 832;
删除编号是 91 的记录,日志位置是 872
|
7.3 恢复的典型错误
如果从服务器宕机,有的时候为了系统恢复,要重装操作系统,这样就可能会导致你的 服务器名称
与之前 不同
。而中继日志里是 包含从服务器名
的。在这种情况下,就可能导致你恢复从服务器的时候,无法从宕机前的中继日志里读取数据,以为是日志文件损坏了,其实是名称不对了。
解决的方法也很简单,把从服务器的名称改回之前的名称。
第18章_主从复制
1. 主从复制概述
1.1 如何提升数据库并发能力
在实际工作中,我们常常将Redis
作为缓存与MySQL
配合来使用,当有请求的时候,首先会从缓存中进行查找,如果存在就直接取出。如果不存在再访问数据库,这样就提升了读取的效率
,也减少了对后端数据库的访问压力
。Redis的缓存架构是高并发架构
中非常重要的一环。
此外,一般应用对数据库而言都是“读多写少
”,也就说对数据库读取数据的压力比较大,有一个思路就是采用数据库集群的方案,做 主从架构
、进行 读写分离
,这样同样可以提升数据库的并发处理能力。但并不是所有的应用都需要对数据库进行主从架构的设置,毕竟设置架构本身是有成本的。
如果我们的目的在于提升数据库高并发访问的效率,那么首先考虑的是如何 优化SQL和索引
,这种方式简单有效;其次才是采用 缓存的策略
,比如使用 Redis将热点数据保存在内存数据库中,提升读取的效率;最后才是对数据库采用 主从架构
,进行读写分离。
按照上面的方式进行优化,使用和维护的成本是由低到高的。
1.2 主从复制的作用
主从同步设计不仅可以提高数据库的吞吐量,还有以下 3 个方面的作用。
**第1个作用:读写分离。**我们可以通过主从复制的方式来同步数据
,然后通过读写分离提高数据库并发处理能力。
其中一个是Master主库,负责写入数据,我们称之为:写库。
其他都是Slave从库,负责读取数据,我们称之为:读库。
当主库进行更新的时候,会自动将数据复制到从库中,而我们在客户端读取数据的时候,会从从库进行读取。
面对“读多写少”的需求,采用读写分离的方式,可以实现更高的并发访问
。同时,我们还能对从服务器进行负载均衡
,让不同的读请求按照策略均匀地分发到不同的从服务器上,让读取更加顺畅
。读取顺畅的另一个原因,就是减少了锁表
的影响,比如我们让主库负责写,当主库出现写锁的时候,不会影响到从库进行SELECT的读取。
**第2个作用就是数据备份。**我们通过主从复制将主库上的数据复制到从库上,相当于一种热备份机制
,也就是在主库正常运行的情况下进行的备份,不会影响到服务。
**第3个作用是具有高可用性。**数据备份实际上是一种冗余的机制,通过这种冗余的方式可以换取数据库的高可用性,也就是当服务器出现故障或宕机的情况下,可以切换到从服务器上,保证服务的正常运行。
2. 主从复制的原理
Slave
会从 Master
读取 binlog
来进行数据同步。
2.1 原理剖析
三个线程
实际上主从同步的原理就是基于 binlog 进行数据同步的。在主从复制过程中,会基于 3 个线程
来操 作,一个主库线程,两个从库线程。
二进制日志转储线程
(Binlog dump thread)是一个主库线程。当从库线程连接的时候, 主库可以将二进制日志发送给从库,当主库读取事件(Event)的时候,会在Binlog上加锁
,读取完成之后,再将锁释放掉。
从库 I/O 线程
会连接到主库,向主库发送请求更新 Binlog。这时从库的 I/O 线程就可以读取到主库的二进制日志转储线程发送的 Binlog 更新部分,并且拷贝到本地的中继日志 (Relay log)。
从库 SQL 线程
会读取从库中的中继日志,并且执行日志中的事件,将从库中的数据与主库保持同步。
注意:
不是所有版本的MySQL都默认开启服务器的二进制日志。在进行主从同步的时候,我们需要先检查服务器是否已经开启了二进制日志。
除非特殊指定,默认情况下从服务器会执行所有主服务器中保存的事件。也可以通过配置,使从服务器执行特定的事件。
复制三步骤
步骤1: Master
将写操作记录到二进制日志( binlog
)。
步骤2: Slave
将 Master
的binary log events拷贝到它的中继日志( relay log
);
步骤3: Slave
重做中继日志中的事件,将改变应用到自己的数据库中。 MySQL复制是异步的且串行化的,而且重启后从 接入点
开始复制。
复制的问题
复制的最大问题: 延时
2.2 复制的基本原则
- 每个
Slave
只有一个 Master
- 每个
Slave
只能有一个唯一的服务器ID
- 每个
Master
可以有多个 Slave
3. 一主一从架构搭建
一台 主机
用于处理所有 写请求
,一台 从机
负责所有 读请求
,架构图如下:
3.1 准备工作
1、准备 2台 CentOS 虚拟机 (具体设置内容在P192)
2、每台虚拟机上需要安装好MySQL (可以是MySQL8.0 )
说明:前面我们讲过如何克隆一台CentOS。大家可以在一台CentOS上安装好MySQL,进而通过克隆的方式复制出1台包含MySQL的虚拟机。
注意:克隆的方式需要修改新克隆出来主机的:① MAC地址
② hostname
③ IP 地址
④ UUID
。
此外,克隆的方式生成的虚拟机(包含MySQL Server),则克隆的虚拟机MySQL Server的UUID相同,必须修改,否则在有些场景会报错。比如: show slave status\G
,报如下的错误:
1
2
|
Last_IO_Error: Fatal error: The slave I/O thread stops because master and slave have
equal MySQL server UUIDs; these UUIDs must be different for replication to work.
|
修改MySQL Server 的UUID方式:
1
2
3
|
vim /var/lib/mysql/auto.cnf
systemctl restart mysqld
|
3.2 主机配置文件
建议mysql版本一致且后台以服务运行,主从所有配置项都配置在 [mysqld]
节点下,且都是小写字母。
具体参数配置如下:
1
2
3
4
5
|
#[必须]主服务器唯一ID
server-id=1
#[必须]启用二进制日志,指名路径。比如:自己本地的路径/log/mysqlbin
log-bin=atguigu-bin
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#[可选] 0(默认)表示读写(主机),1表示只读(从机)
read-only=0
#设置日志文件保留的时长,单位是秒
binlog_expire_logs_seconds=6000
#控制单个二进制日志大小。此参数的最大和默认值是1GB
max_binlog_size=200M
#[可选]设置不要复制的数据库
binlog-ignore-db=test
#[可选]设置需要复制的数据库,默认全部记录。比如:binlog-do-db=atguigu_master_slave
binlog-do-db=需要复制的主数据库名字
#[可选]设置binlog格式
binlog_format=STATEMENT
|
重启后台mysql服务,使配置生效。
注意:
先搭建完主从复制,再创建数据库。
MySQL主从复制起始时,从机不继承主机数据。
① binlog格式设置:
格式1: STATEMENT模式
(基于SQL语句的复制(statement-based replication, SBR))
1
|
binlog_format=STATEMENT
|
每一条会修改数据的sql语句会记录到binlog中。这是默认的binlog格式。
- SBR 的优点:
- 历史悠久,技术成熟
- 不需要记录每一行的变化,减少了binlog日志量,文件较小
- binlog中包含了所有数据库更改信息,可以据此来审核数据库的安全等情况
- binlog可以用于实时的还原,而不仅仅用于复制
- 主从版本可以不一样,从服务器版本可以比主服务器版本高
- SBR 的缺点:
- 不是所有的UPDATE语句都能被复制,尤其是包含不确定操作的时候
- 使用以下函数的语句也无法被复制:LOAD_FILE()、UUID()、USER()、FOUND_ROWS()、SYSDATE() (除非启动时启用了 –sysdate-is-now 选项)
- INSERT … SELECT 会产生比 RBR 更多的行级锁
- 复制需要进行全表扫描(WHERE 语句中没有使用到索引)的 UPDATE 时,需要比 RBR 请求更多的行级锁
- 对于有 AUTO_INCREMENT 字段的 InnoDB表而言,INSERT 语句会阻塞其他 INSERT 语句
- 对于一些复杂的语句,在从服务器上的耗资源情况会更严重,而 RBR 模式下,只会对那个发 生变化的记录产生影响
- 执行复杂语句如果出错的话,会消耗更多资源
- 数据表必须几乎和主服务器保持一致才行,否则可能会导致复制出错
② ROW模式(基于行的复制(row-based replication, RBR))
5.1.5版本的MySQL才开始支持,不记录每条sql语句的上下文信息,仅记录哪条数据被修改了,修改成什么样了。
- RBR 的优点:
- 任何情况都可以被复制,这对复制来说是最
安全可靠
的。(比如:不会出现某些特定情况下 的存储过程、function、trigger的调用和触发无法被正确复制的问题)
- 多数情况下,从服务器上的表如果有主键的话,复制就会快了很多
- 复制以下几种语句时的行锁更少:INSERT … SELECT、包含 AUTO_INCREMENT 字段的 INSERT、 没有附带条件或者并没有修改很多记录的 UPDATE 或 DELETE 语句
- 执行 INSERT,UPDATE,DELETE 语句时锁更少
- 从服务器上采用 多线程 来执行复制成为可能
- RBR 的缺点:
- binlog 大了很多
- 复杂的回滚时 binlog 中会包含大量的数据
- 主服务器上执行 UPDATE 语句时,所有发生变化的记录都会写到 binlog 中,而 SBR 只会写一次,这会导致频繁发生 binlog 的并发写问题
- 无法从 binlog 中看到都复制了些什么语句
③ MIXED模式(混合模式复制(mixed-based replication, MBR))
从5.1.8版本开始,MySQL提供了Mixed格式,实际上就是Statement与Row的结合。
在Mixed模式下,一般的语句修改使用statment格式保存binlog。如一些函数,statement无法完成主从复制的操作,则采用row格式保存binlog。
MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式,也就是在Statement和Row之间选择一种。
3.3 从机配置文件
要求主从所有配置项都配置在 my.cnf
的 [mysqld]
栏位下,且都是小写字母。
-
必选
1
2
|
#[必须]从服务器唯一ID
server-id=2
|
-
可选
1
2
|
#[可选]启用中继日志
relay-log=mysql-relay
|
重启后台mysql服务,使配置生效。
注意:主从机都关闭防火墙
service iptables stop #CentOS 6
systemctl stop firewalld.service #CentOS 7
3.4 主机:建立账户并授权
1
2
|
#在主机MySQL里执行授权主从复制的命令
GRANT REPLICATION SLAVE ON *.* TO 'slave1'@'从机器数据库IP' IDENTIFIED BY 'abc123'; #5.5,5.7
|
注意:如果使用的是MySQL8,需要如下的方式建立账户,并授权slave:
1
2
3
4
5
6
7
8
|
CREATE USER 'slave1'@'%' IDENTIFIED BY '123456';
GRANT REPLICATION SLAVE ON *.* TO 'slave1'@'%';
#此语句必须执行。否则见下面。
ALTER USER 'slave1'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
flush privileges;
|
注意:在从机执行show slave status\G时报错:
Last_IO_Error: error connecting to master ‘slave1@192.168.1.150:3306’ - retry-time: 60 retries: 1 message:
Authentication plugin ‘caching_sha2_password’ reported error: Authentication requires secure connection.
查询Master的状态,并记录下File和Position的值。
注意:执行完此步骤后不要再操作主服务器MySQL,防止主服务器状态值变化。
3.5 从机:配置需要复制的主机
**步骤1:**从机上复制主机的命令
1
2
3
4
5
6
|
CHANGE MASTER TO
MASTER_HOST='主机的IP地址',
MASTER_USER='主机用户名',
MASTER_PASSWORD='主机用户名的密码',
MASTER_LOG_FILE='mysql-bin.具体数字',
MASTER_LOG_POS=具体值;
|
举例:
1
2
3
|
CHANGE MASTER TO
MASTER_HOST='192.168.1.150',MASTER_USER='slave1',MASTER_PASSWORD='123456',MASTER_LOG_F
ILE='atguigu-bin.000007',MASTER_LOG_POS=154;
|
步骤2:
1
2
|
#启动slave同步
START SLAVE;
|
如果报错:
可以执行如下操作,删除之前的relay_log信息。然后重新执行 CHANGE MASTER TO …语句即可。
1
|
mysql> reset slave; #删除SLAVE数据库的relaylog日志文件,并重新启用新的relaylog文件
|
接着,查看同步状态:
上面两个参数都是Yes,则说明主从配置成功!
显式如下的情况,就是不正确的。可能错误的原因有:
1
2
3
4
5
6
|
1. 网络不通
2. 账户密码错误
3. 防火墙
4. mysql配置文件问题
5. 连接服务器时语法
6. 主服务器mysql权限
|
3.6 测试
主机新建库、新建表、insert记录,从机复制:
1
2
3
4
5
6
7
|
CREATE DATABASE atguigu_master_slave;
CREATE TABLE mytbl(id INT,NAME VARCHAR(16));
INSERT INTO mytbl VALUES(1, 'zhang3');
INSERT INTO mytbl VALUES(2,@@hostname);
|
3.7 停止主从同步
重新配置主从,需要在从机上执行:
1
2
3
|
stop slave;
reset master; #删除Master中所有的binglog文件,并将日志索引文件清空,重新开始所有新的日志文件(慎用)
|
3.8 后续
搭建主从复制:双主双从
一个主机m1用于处理所有写请求,它的从机s1和另一台主机m2还有它的从机s2负责所有读请求。当m1主机宕机后,m2主机负责写请求,m1、m2互为备机。结构图如下:
4. 同步数据一致性问题
主从同步的要求:
- 读库和写库的数据一致(最终一致);
- 写数据必须写到写库;
- 读数据必须到读库(不一定);
4.1 理解主从延迟问题
进行主从同步的内容是二进制日志,它是一个文件,在进行 网络传输
的过程中就一定会 存在主从延迟
(比如 500ms),这样就可能造成用户在从库上读取的数据不是最新的数据,也就是主从同步中的 数据不一致性
问题。
4.2 主从延迟问题原因
在网络正常的时候,日志从主库传给从库所需的时间是很短的,即T2-T1的值是非常小的。即,网络正常情况下,主备延迟的主要来源是备库接收完binlog和执行完这个事务之间的时间差。
**主备延迟最直接的表现是,从库消费中继日志(relay log)的速度,比主库生产binlog的速度要慢。**造成原因:
1、从库的机器性能比主库要差
2、从库的压力大
3、大事务的执行
**举例1:**一次性用delete语句删除太多数据
结论:后续再删除数据的时候,要控制每个事务删除的数据量,分成多次删除。
**举例2:**一次性用insert…select插入太多数据
**举例3:**大表DDL
比如在主库对一张500W的表添加一个字段耗费了10分钟,那么从节点上也会耗费10分钟。
4.3 如何减少主从延迟
若想要减少主从延迟的时间,可以采取下面的办法:
- 降低多线程大事务并发的概率,优化业务逻辑
- 优化SQL,避免慢SQL,
减少批量操作
,建议写脚本以update-sleep这样的形式完成。
提高从库机器的配置
,减少主库写binlog和从库读binlog的效率差。
- 尽量采用
短的链路
,也就是主库和从库服务器的距离尽量要短,提升端口带宽,减少binlog传输的网络延时。
- 实时性要求的业务读强制走主库,从库只做灾备,备份。
4.4 如何解决一致性问题
如果操作的数据存储在同一个数据库中,那么对数据进行更新的时候,可以对记录加写锁,这样在读取的时候就不会发生数据不一致的情况。但这时从库的作用就是 备份
,并没有起到 读写分离
,分担主库 读压力
的作用。
读写分离情况下,解决主从同步中数据不一致的问题, 就是解决主从之间 数据复制方式
的问题,如果按照数据一致性 从弱到强
来进行划分,有以下 3 种复制方式。
方法 1:异步复制
异步模式就是客户端提交 COMMIT 之后不需要等从库返回任何结果,而是直接将结果返回给客户端,这样做的好处是不会影响主库写的效率,但可能会存在主库宕机,而Binlog还没有同步到从库的情况,也就是此时的主库和从库数据不一致。这时候从从库中选择一个作为新主,那么新主则可能缺少原来主服务器中已提交的事务。所以,这种复制模式下的数据一致性是最弱的。
方法 2:半同步复制
方法 3:组复制
异步复制和半同步复制都无法最终保证数据的一致性问题,半同步复制是通过判断从库响应的个数来决定是否返回给客户端,虽然数据一致性相比于异步复制有提升,但仍然无法满足对数据一致性要求高的场景,比如金融领域。MGR 很好地弥补了这两种复制模式的不足。
组复制技术,简称 MGR(MySQL Group Replication)。是 MySQL 在 5.7.17 版本中推出的一种新的数据复制技术,这种复制技术是基于 Paxos 协议的状态机复制。
MGR 是如何工作的
首先我们将多个节点共同组成一个复制组,在 执行读写(RW)事务
的时候,需要通过一致性协议层 (Consensus 层)的同意,也就是读写事务想要进行提交,必须要经过组里“大多数人”(对应 Node 节 点)的同意,大多数指的是同意的节点数量需要大于 (N/2+1),这样才可以进行提交,而不是原发起方一个说了算。而针对 只读(RO)事务
则不需要经过组内同意,直接 COMMIT 即可。
在一个复制组内有多个节点组成,它们各自维护了自己的数据副本,并且在一致性协议层实现了原子消 息和全局有序消息,从而保证组内数据的一致性。
MGR 将 MySQL 带入了数据强一致性的时代,是一个划时代的创新,其中一个重要的原因就是MGR 是基 于 Paxos 协议的。Paxos 算法是由 2013 年的图灵奖获得者 Leslie Lamport 于 1990 年提出的,有关这个算法的决策机制可以搜一下。事实上,Paxos 算法提出来之后就作为 分布式一致性算法
被广泛应用,比如 Apache 的 ZooKeeper 也是基于 Paxos 实现的。
5. 知识延伸
在主从架构的配置中,如果想要采取读写分离的策略,我们可以 自己编写程序
,也可以通过 第三方的中间件
来实现。
- 自己编写程序的好处就在于比较自主,我们可以自己判断哪些查询在从库上来执行,针对实时性要 求高的需求,我们还可以考虑哪些查询可以在主库上执行。同时,程序直接连接数据库,减少了中间件层,相当于减少了性能损耗。
- 采用中间件的方法有很明显的优势,
功能强大
, 使用简单
。但因为在客户端和数据库之间增加了 中间件层会有一些 性能损耗
,同时商业中间件也是有使用成本的。我们也可以考虑采取一些优秀的开源工具。
① Cobar
属于阿里B2B事业群,始于2008年,在阿里服役3年多,接管3000+个MySQL数据库的 schema,集群日处理在线SQL请求50亿次以上。由于Cobar发起人的离职,Cobar停止维护。
② Mycat
是开源社区在阿里cobar基础上进行二次开发,解决了cobar存在的问题,并且加入了许多新的功能在其中。青出于蓝而胜于蓝。
③ OneProxy
基于MySQL官方的proxy思想利用c语言进行开发的,OneProxy是一款商业 收费 的中 间件。舍弃了一些功能,专注在 性能和稳定性上 。
④ kingshard
由小团队用go语言开发,还需要发展,需要不断完善。
⑤ Vitess
是Youtube生产在使用,架构很复杂。不支持MySQL原生协议,使用 需要大量改造成 本 。
⑥ Atlas
是360团队基于mysql proxy改写,功能还需完善,高并发下不稳定。
⑦ MaxScale
是mariadb(MySQL原作者维护的一个版本) 研发的中间件
⑧ MySQLRoute
是MySQL官方Oracle公司发布的中间件
主备切换:
- 主动切换
- 被动切换
- 如何判断主库出问题了?如何解决过程中的数据不一致性问题 ?
第19章_数据库备份与恢复
1. 物理备份与逻辑备份
**物理备份:**备份数据文件,转储数据库物理文件到某一目录。物理备份恢复速度比较快,但占用空间比较大,MySQL中可以用 xtrabackup
工具来进行物理备份。
**逻辑备份:**对数据库对象利用工具进行导出工作,汇总入备份文件内。逻辑备份恢复速度慢,但占用空间小,更灵活。MySQL 中常用的逻辑备份工具为 mysqldump
。逻辑备份就是 备份sql语句
,在恢复的 时候执行备份的sql语句实现数据库数据的重现。
2. mysqldump实现逻辑备份
mysqldump是MySQL提供的一个非常有用的数据库备份工具。
2.1 备份一个数据库
mysqldump命令执行时,可以将数据库备份成一个文本文件
,该文件中实际上包含多个CREATE
和INSERT
语句,使用这些语句可以重新创建表和插入数据。
- 查出需要备份的表的结构,在文本文件中生成一个CREATE语句
- 将表中的所有记录转换为一条INSERT语句。
基本语法:
1
|
mysqldump –u 用户名称 –h 主机名称 –p密码 待备份的数据库名称[tbname, [tbname...]]> 备份文件名称.sql
|
说明: 备份的文件并非一定要求后缀名为.sql,例如后缀名为.txt的文件也是可以的。
举例:使用root用户备份atguigu数据库:
1
|
mysqldump -uroot -p atguigu>atguigu.sql #备份文件存储在当前目录下
|
1
|
mysqldump -uroot -p atguigudb1 > /var/lib/mysql/atguigu.sql
|
备份文件剖析:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
-- MySQL dump 10.13 Distrib 8.0.26, for Linux (x86_64)
--
-- Host: localhost Database: atguigu
-- ------------------------------------------------------
-- Server version 8.0.26
/*!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: `atguigu`
--
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `atguigu` /*!40100 DEFAULT CHARACTER SET
utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `atguigu`;
--
-- Table structure for table `student`
--
DROP TABLE IF EXISTS `student`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `student` (
`studentno` int NOT NULL,
`name` varchar(20) DEFAULT NULL,
`class` varchar(20) DEFAULT NULL,
PRIMARY KEY (`studentno`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
/*!40101 SET character_set_client = @saved_cs_client */;
INSERT INTO `student` VALUES (1,'张三_back','一班'),(3,'李四','一班'),(8,'王五','二班'),
(15,'赵六','二班'),(20,'钱七','>三班'),(22,'zhang3_update','1ban'),(24,'wang5','2ban');
/*!40000 ALTER TABLE `student` ENABLE KEYS */;
UNLOCK TABLES;
.
.
.
.
/*!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 2022-01-07 9:58:23
|
2.2 备份全部数据库
若想用mysqldump备份整个实例,可以使用 –all-databases 或 -A 参数:
1
2
|
mysqldump -uroot -pxxxxxx --all-databases > all_database.sql
mysqldump -uroot -pxxxxxx -A > all_database.sql
|
2.3 备份部分数据库
使用 --databases
或 -B
参数了,该参数后面跟数据库名称,多个数据库间用空格隔开。如果指定 databases参数,备份文件中会存在创建数据库的语句,如果不指定参数,则不存在。语法如下:
1
|
mysqldump –u user –h host –p --databases [数据库的名称1 [数据库的名称2...]] > 备份文件名称.sql
|
举例:
1
|
mysqldump -uroot -p --databases atguigu atguigu12 >two_database.sql
|
或
1
|
mysqldump -uroot -p -B atguigu atguigu12 > two_database.sql
|
2.4 备份部分表
比如,在表变更前做个备份。语法如下:
1
|
mysqldump –u user –h host –p 数据库的名称 [表名1 [表名2...]] > 备份文件名称.sql
|
举例:备份atguigu数据库下的book表
1
|
mysqldump -uroot -p atguigu book> book.sql
|
book.sql文件内容如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
mysqldump -uroot -p atguigu book> book.sql^C
[root@node1 ~]# ls
kk kubekey kubekey-v1.1.1-linux-amd64.tar.gz README.md test1.sql two_database.sql
[root@node1 ~]# mysqldump -uroot -p atguigu book> book.sql
Enter password:
[root@node1 ~]# ls
book.sql kk kubekey kubekey-v1.1.1-linux-amd64.tar.gz README.md test1.sql
two_database.sql
[root@node1 ~]# vi book.sql
-- MySQL dump 10.13 Distrib 8.0.26, for Linux (x86_64)
--
-- Host: localhost Database: atguigu
-- ------------------------------------------------------
-- Server version 8.0.26
/*!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 `book`
--
DROP TABLE IF EXISTS `book`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `book` (
`bookid` int unsigned NOT NULL AUTO_INCREMENT,
`card` int unsigned NOT NULL,
`test` varchar(255) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`bookid`),
KEY `Y` (`card`)
) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `book`
--
LOCK TABLES `book` WRITE;
/*!40000 ALTER TABLE `book` DISABLE KEYS */;
INSERT INTO `book` VALUES (1,9,NULL),(2,10,NULL),(3,4,NULL),(4,8,NULL),(5,7,NULL),
(6,10,NULL),(7,11,NULL),(8,3,NULL),(9,1,NULL),(10,17,NULL),(11,19,NULL),(12,4,NULL),
(13,1,NULL),(14,14,NULL),(15,5,NULL),(16,5,NULL),(17,8,NULL),(18,3,NULL),(19,12,NULL),
(20,11,NULL),(21,9,NULL),(22,20,NULL),(23,13,NULL),(24,3,NULL),(25,18,NULL),
(26,20,NULL),(27,5,NULL),(28,6,NULL),(29,15,NULL),(30,15,NULL),(31,12,NULL),
(32,11,NULL),(33,20,NULL),(34,5,NULL),(35,4,NULL),(36,6,NULL),(37,17,NULL),
(38,5,NULL),(39,16,NULL),(40,6,NULL),(41,18,NULL),(42,12,NULL),(43,6,NULL),
(44,12,NULL),(45,2,NULL),(46,12,NULL),(47,15,NULL),(48,17,NULL),(49,2,NULL),
(50,16,NULL),(51,13,NULL),(52,17,NULL),(53,7,NULL),(54,2,NULL),(55,9,NULL),
(56,1,NULL),(57,14,NULL),(58,7,NULL),(59,15,NULL),(60,12,NULL),(61,13,NULL),
(62,8,NULL),(63,2,NULL),(64,6,NULL),(65,2,NULL),(66,12,NULL),(67,12,NULL),(68,4,NULL),
(69,5,NULL),(70,10,NULL),(71,16,NULL),(72,8,NULL),(73,14,NULL),(74,5,NULL),
(75,4,NULL),(76,3,NULL),(77,2,NULL),(78,2,NULL),(79,2,NULL),(80,3,NULL),(81,8,NULL),
(82,14,NULL),(83,5,NULL),(84,4,NULL),(85,2,NULL),(86,20,NULL),(87,12,NULL),
(88,1,NULL),(89,8,NULL),(90,18,NULL),(91,3,NULL),(92,3,NULL),(93,6,NULL),(94,1,NULL),
(95,4,NULL),(96,17,NULL),(97,15,NULL),(98,1,NULL),(99,20,NULL),(100,15,NULL);
/*!40000 ALTER TABLE `book` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
可以看到,book文件和备份的库文件类似。不同的是,book文件只包含book表的DROP、CREATE和 INSERT语句。
备份多张表使用下面的命令,比如备份book和account表:
1
2
|
#备份多张表
mysqldump -uroot -p atguigu book account > 2_tables_bak.sql
|
2.5 备份单表的部分数据
有些时候一张表的数据量很大,我们只需要部分数据。这时就可以使用 –where 选项了。where后面附带需要满足的条件。
举例:备份student表中id小于10的数据:
1
|
mysqldump -uroot -p atguigu student --where="id < 10 " > student_part_id10_low_bak.sql
|
内容如下所示,insert语句只有id小于10的部分
1
2
3
4
5
6
|
LOCK TABLES `student` WRITE;
/*!40000 ALTER TABLE `student` DISABLE KEYS */;
INSERT INTO `student` VALUES (1,100002,'JugxTY',157,280),(2,100003,'QyUcCJ',251,277),
(3,100004,'lATUPp',80,404),(4,100005,'BmFsXI',240,171),(5,100006,'mkpSwJ',388,476),
(6,100007,'ujMgwN',259,124),(7,100008,'HBJTqX',429,168),(8,100009,'dvQSQA',61,504),
(9,100010,'HljpVJ',234,185);
|
2.6 排除某些表的备份
如果我们想备份某个库,但是某些表数据量很大或者与业务关联不大,这个时候可以考虑排除掉这些表,同样的,选项 --ignore-table
可以完成这个功能。
1
|
mysqldump -uroot -p atguigu --ignore-table=atguigu.student > no_stu_bak.sql
|
通过如下指定判定文件中没有student表结构:
1
|
grep "student" no_stu_bak.sql
|
2.7 只备份结构或只备份数据
只备份结构的话可以使用 --no-data
简写为 -d
选项;只备份数据可以使用 --no-create-info
简写为 -t
选项。
-
只备份结构
1
2
3
4
|
mysqldump -uroot -p atguigu --no-data > atguigu_no_data_bak.sql
#使用grep命令,没有找到insert相关语句,表示没有数据备份。
[root@node1 ~]# grep "INSERT" atguigu_no_data_bak.sql
[root@node1 ~]#
|
-
只备份数据
1
2
3
4
|
mysqldump -uroot -p atguigu --no-create-info > atguigu_no_create_info_bak.sql
#使用grep命令,没有找到create相关语句,表示没有数据结构。
[root@node1 ~]# grep "CREATE" atguigu_no_create_info_bak.sql
[root@node1 ~]#
|
2.8 备份中包含存储过程、函数、事件
mysqldump备份默认是不包含存储过程,自定义函数及事件的。可以使用 --routines
或 -R
选项来备份存储过程及函数,使用 --events
或 -E
参数来备份事件。
举例:备份整个atguigu库,包含存储过程及事件:
- 使用下面的SQL可以查看当前库有哪些存储过程或者函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
mysql> SELECT SPECIFIC_NAME,ROUTINE_TYPE ,ROUTINE_SCHEMA FROM
information_schema.Routines WHERE ROUTINE_SCHEMA="atguigu";
+---------------+--------------+----------------+
| SPECIFIC_NAME | ROUTINE_TYPE | ROUTINE_SCHEMA |
+---------------+--------------+----------------+
| rand_num | FUNCTION | atguigu |
| rand_string | FUNCTION | atguigu |
| BatchInsert | PROCEDURE | atguigu |
| insert_class | PROCEDURE | atguigu |
| insert_order | PROCEDURE | atguigu |
| insert_stu | PROCEDURE | atguigu |
| insert_user | PROCEDURE | atguigu |
| ts_insert | PROCEDURE | atguigu |
+---------------+--------------+----------------+
9 rows in set (0.02 sec)
|
下面备份atguigu库的数据,函数以及存储过程。
1
|
mysqldump -uroot -p -R -E --databases atguigu > fun_atguigu_bak.sql
|
查询备份文件中是否存在函数,如下所示,可以看到确实包含了函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
grep -C 5 "rand_num" fun_atguigu_bak.sql
--
--
-- Dumping routines for database 'atguigu'
--
/*!50003 DROP FUNCTION IF EXISTS `rand_num` */;
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client = utf8mb3 */ ;
/*!50003 SET character_set_results = utf8mb3 */ ;
/*!50003 SET collation_connection = utf8_general_ci */ ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode =
'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISIO
N_BY_ZERO,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
CREATE DEFINER=`root`@`%` FUNCTION `rand_num`(from_num BIGINT ,to_num BIGINT) RETURNS
bigint
BEGIN
DECLARE i BIGINT DEFAULT 0;
SET i = FLOOR(from_num +RAND()*(to_num - from_num+1)) ;
RETURN i;
END ;;
--
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO class ( classname,address,monitor ) VALUES
(rand_string(8),rand_string(10),rand_num());
UNTIL i = max_num
END REPEAT;
COMMIT;
END ;;
DELIMITER ;
--
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0; #设置手动提交事务
REPEAT #循环
SET i = i + 1; #赋值
INSERT INTO order_test (order_id, trans_id ) VALUES
(rand_num(1,7000000),rand_num(100000000000000000,700000000000000000));
UNTIL i = max_num
END REPEAT;
COMMIT; #提交事务
END ;;
DELIMITER ;
--
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0; #设置手动提交事务
REPEAT #循环
SET i = i + 1; #赋值
INSERT INTO student (stuno, name ,age ,classId ) VALUES
((START+i),rand_string(6),rand_num(),rand_num());
UNTIL i = max_num
END REPEAT;
COMMIT; #提交事务
END ;;
DELIMITER ;
--
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO `user` ( name,age,sex ) VALUES ("atguigu",rand_num(1,20),"male");
UNTIL i = max_num
END REPEAT;
COMMIT;
END ;;
DELIMITER ;
|
2.9 mysqldump常用选项
mysqldump其他常用选项如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
--add-drop-database:在每个CREATE DATABASE语句前添加DROP DATABASE语句。
--add-drop-tables:在每个CREATE TABLE语句前添加DROP TABLE语句。
--add-locking:用LOCK TABLES和UNLOCK TABLES语句引用每个表转储。重载转储文件时插入得更快。
--all-database, -A:转储所有数据库中的所有表。与使用--database选项相同,在命令行中命名所有数据库。
--comment[=0|1]:如果设置为0,禁止转储文件中的其他信息,例如程序版本、服务器版本和主机。--skipcomments与--comments=0的结果相同。默认值为1,即包括额外信息。
--compact:产生少量输出。该选项禁用注释并启用--skip-add-drop-tables、--no-set-names、--skipdisable-keys和--skip-add-locking选项。
--compatible=name:产生与其他数据库系统或旧的MySQL服务器更兼容的输出,值可以为ansi、MySQL323、MySQL40、postgresql、oracle、mssql、db2、maxdb、no_key_options、no_table_options或者no_field_options。
--complete_insert, -c:使用包括列名的完整的INSERT语句。
--debug[=debug_options], -#[debug_options]:写调试日志。
--delete,-D:导入文本文件前清空表。
--default-character-set=charset:使用charsets默认字符集。如果没有指定,就使用utf8。
--delete--master-logs:在主复制服务器上,完成转储操作后删除二进制日志。该选项自动启用-masterdata。
--extended-insert,-e:使用包括几个VALUES列表的多行INSERT语法。这样使得转储文件更小,重载文件时可以加速插入。
--flush-logs,-F:开始转储前刷新MySQL服务器日志文件。该选项要求RELOAD权限。
--force,-f:在表转储过程中,即使出现SQL错误也继续。
--lock-all-tables,-x:对所有数据库中的所有表加锁。在整体转储过程中通过全局锁定来实现。该选项自动关闭--single-transaction和--lock-tables。
--lock-tables,-l:开始转储前锁定所有表。用READ LOCAL锁定表以允许并行插入MyISAM表。对于事务表(例如InnoDB和BDB),--single-transaction是一个更好的选项,因为它根本不需要锁定表。
--no-create-db,-n:该选项禁用CREATE DATABASE /*!32312 IF NOT EXIST*/db_name语句,如果给出--database或--all-database选项,就包含到输出中。
--no-create-info,-t:只导出数据,而不添加CREATE TABLE语句。
--no-data,-d:不写表的任何行信息,只转储表的结构。
--opt:该选项是速记,它可以快速进行转储操作并产生一个能很快装入MySQL服务器的转储文件。该选项默认开启,但可以用--skip-opt禁用。
--password[=password],-p[password]:当连接服务器时使用的密码。
-port=port_num,-P port_num:用于连接的TCP/IP端口号。
--protocol={TCP|SOCKET|PIPE|MEMORY}:使用的连接协议。
--replace,-r –replace和--ignore:控制替换或复制唯一键值已有记录的输入记录的处理。如果指定--replace,新行替换有相同的唯一键值的已有行;如果指定--ignore,复制已有的唯一键值的输入行被跳过。如果不指定这两个选项,当发现一个复制键值时会出现一个错误,并且忽视文本文件的剩余部分。
--silent,-s:沉默模式。只有出现错误时才输出。
--socket=path,-S path:当连接localhost时使用的套接字文件(为默认主机)。
--user=user_name,-u user_name:当连接服务器时MySQL使用的用户名。
--verbose,-v:冗长模式,打印出程序操作的详细信息。
--xml,-X:产生XML输出。
|
运行帮助命令 mysqldump --help
,可以获得特定版本的完整选项列表。
提示 如果运行mysqldump没有–quick或–opt选项,mysqldump在转储结果前将整个结果集装入内 存。如果转储大数据库可能会出现问题,该选项默认启用,但可以用–skip-opt禁用。如果使用最 新版本的mysqldump程序备份数据,并用于恢复到比较旧版本的MySQL服务器中,则不要使用–opt 或-e选项。
3. mysql命令恢复数据
使用mysqldump命令将数据库中的数据备份成一个文本文件。需要恢复时,可以使用mysql命令
来恢复备份的数据。
mysql命令可以执行备份文件中的CREATE语句
和INSERT语句
。通过CREATE语句来创建数据库和表。通过INSERT语句来插入备份的数据。
基本语法:
1
|
mysql –u root –p [dbname] < backup.sql
|
其中,dbname参数表示数据库名称。该参数是可选参数,可以指定数据库名,也可以不指定。指定数据库名时,表示还原该数据库下的表。此时需要确保MySQL服务器中已经创建了该名的数据库。不指定数据库名,表示还原文件中所有的数据库。此时sql文件中包含有CREATE DATABASE语句,不需要MySQL服务器中已存在的这些数据库。
3.1 单库备份中恢复单库
使用root用户,将之前练习中备份的atguigu.sql文件中的备份导入数据库中,命令如下:
如果备份文件中包含了创建数据库的语句,则恢复的时候不需要指定数据库名称,如下所示
1
|
mysql -uroot -p < atguigu.sql
|
否则需要指定数据库名称,如下所示
1
|
mysql -uroot -p atguigu4< atguigu.sql
|
3.2 全量备份恢复
如果我们现在有昨天的全量备份,现在想整个恢复,则可以这样操作:
1
|
mysql –u root –p < all.sql
|
1
|
mysql -uroot -pxxxxxx < all.sql
|
执行完后,MySQL数据库中就已经恢复了all.sql文件中的所有数据库。
3.3 全量备份恢复
可能有这样的需求,比如说我们只想恢复某一个库,但是我们有的是整个实例的备份,这个时候我们可以从全量备份中分离出单个库的备份。
举例:
1
2
|
sed -n '/^-- Current Database: `atguigu`/,/^-- Current Database: `/p' all_database.sql > atguigu.sql
#分离完成后我们再导入atguigu.sql即可恢复单个库
|
3.4 从单库备份中恢复单表
这个需求还是比较常见的。比如说我们知道哪个表误操作了,那么就可以用单表恢复的方式来恢复。
举例:我们有atguigu整库的备份,但是由于class表误操作,需要单独恢复出这张表。
1
2
3
4
5
6
7
8
9
10
|
cat atguigu.sql | sed -e '/./{H;$!d;}' -e 'x;/CREATE TABLE `class`/!d;q' > class_structure.sql
cat atguigu.sql | grep --ignore-case 'insert into `class`' > class_data.sql
#用shell语法分离出创建表的语句及插入数据的语句后 再依次导出即可完成恢复
use atguigu;
mysql> source class_structure.sql;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> source class_data.sql;
Query OK, 1 row affected (0.01 sec)
|
4. 物理备份:直接复制整个数据库
直接将MySQL中的数据库文件复制出来。这种方法最简单,速度也最快。MySQL的数据库目录位置不一 定相同:
- 在Windows平台下,MySQL 8.0存放数据库的目录通常默认为 “ C:\ProgramData\MySQL\MySQL Server 8.0\Data ”或者其他用户自定义目录;
- 在Linux平台下,数据库目录位置通常为/var/lib/mysql/;
- 在MAC OSX平台下,数据库目录位置通常为“/usr/local/mysql/data”
但为了保证备份的一致性。需要保证:
- 方式1:备份前,将服务器停止。
- 方式2:备份前,对相关表执行 FLUSH TABLES WITH READ LOCK 操作。这样当复制数据库目录中 的文件时,允许其他客户继续查询表。同时,FLUSH TABLES语句来确保开始备份前将所有激活的索 引页写入硬盘。
这种方式方便、快速,但不是最好的备份方法,因为实际情况可能 不允许停止MySQL服务器
或者 锁住表
,而且这种方法 对InnoDB存储引擎 的表不适用。对于MyISAM存储引擎的表,这样备份和还原很方便,但是还原时最好是相同版本的MySQL数据库,否则可能会存在文件类型不同的情况。
注意,物理备份完毕后,执行 UNLOCK TABLES 来结算其他客户对表的修改行为。
说明: 在MySQL版本号中,第一个数字表示主版本号,主版本号相同的MySQL数据库文件格式相同。
此外,还可以考虑使用相关工具实现备份。比如, MySQLhotcopy
工具。MySQLhotcopy是一个Perl脚本,它使用LOCK TABLES、FLUSH TABLES和cp或scp来快速备份数据库。它是备份数据库或单个表最快的途径,但它只能运行在数据库目录所在的机器上,并且只能备份MyISAM类型的表。多用于mysql5.5之前。
5. 物理恢复:直接复制到数据库目录
步骤:
1)演示删除备份的数据库中指定表的数据
2)将备份的数据库数据拷贝到数据目录下,并重启MySQL服务器
3)查询相关表的数据是否恢复。需要使用下面的 chown
操作。
要求:
- 必须确保备份数据的数据库和待恢复的数据库服务器的主版本号相同。
- 因为只有MySQL数据库主版本号相同时,才能保证这两个MySQL数据库文件类型是相同的。
- 这种方式对
MyISAM类型的表比较有效
,对于InnoDB类型的表则不可用。
- 在Linux操作系统下,复制到数据库目录后,一定要将数据库的用户和组变成mysql,命令如下:
1
|
chown -R mysql.mysql /var/lib/mysql/dbname
|
其中,两个mysql分别表示组和用户;“-R”参数可以改变文件夹下的所有子文件的用户和组;“dbname”参数表示数据库目录。
提示 Linux操作系统下的权限设置非常严格。通常情况下,MySQL数据库只有root用户和mysql用户 组下的mysql用户才可以访问,因此将数据库目录复制到指定文件夹后,一定要使用chown命令将 文件夹的用户组变为mysql,将用户变为mysql。
6. 表的导出与导入
6.1 表的导出
1. 使用SELECT…INTO OUTFILE导出文本文件
在MySQL中,可以使用SELECT…INTO OUTFILE语句将表的内容导出成一个文本文件。
**举例:**使用SELECT…INTO OUTFILE将atguigu数据库中account表中的记录导出到文本文件。
(1)选择数据库atguigu,并查询account表,执行结果如下所示。
1
2
3
4
5
6
7
8
9
10
11
|
use atguigu;
select * from account;
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 90 |
| 2 | 李四 | 100 |
| 3 | 王五 | 0 |
+----+--------+---------+
3 rows in set (0.01 sec)
|
(2)mysql默认对导出的目录有权限限制,也就是说使用命令行进行导出的时候,需要指定目录进行操作。
查询secure_file_priv值:
1
2
3
4
5
6
7
8
|
mysql> SHOW GLOBAL VARIABLES LIKE '%secure%';
+--------------------------+-----------------------+
| Variable_name | Value |
+--------------------------+-----------------------+
| require_secure_transport | OFF |
| secure_file_priv | /var/lib/mysql-files/ |
+--------------------------+-----------------------+
2 rows in set (0.02 sec)
|
(3)上面结果中显示,secure_file_priv变量的值为/var/lib/mysql-files/,导出目录设置为该目录,SQL语句如下。
1
|
SELECT * FROM account INTO OUTFILE "/var/lib/mysql-files/account.txt";
|
(4)查看 /var/lib/mysql-files/account.txt`文件。
1
2
3
|
1 张三 90
2 李四 100
3 王五 0
|
2. 使用mysqldump命令导出文本文件
**举例1:**使用mysqldump命令将将atguigu数据库中account表中的记录导出到文本文件:
1
|
mysqldump -uroot -p -T "/var/lib/mysql-files/" atguigu account
|
mysqldump命令执行完毕后,在指定的目录/var/lib/mysql-files/下生成了account.sql和account.txt文件。
打开account.sql文件,其内容包含创建account表的CREATE语句。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
[root@node1 mysql-files]# cat account.sql
-- MySQL dump 10.13 Distrib 8.0.26, for Linux (x86_64)
--
-- Host: localhost Database: atguigu
-- ------------------------------------------------------
-- Server version 8.0.26
/*!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' */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `account`
--
DROP TABLE IF EXISTS `account`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `account` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`balance` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!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 2022-01-07 23:19:27
|
打开account.txt文件,其内容只包含account表中的数据。
1
2
3
4
|
[root@node1 mysql-files]# cat account.txt
1 张三 90
2 李四 100
3 王五 0
|
**举例2:**使用mysqldump将atguigu数据库中的account表导出到文本文件,使用FIELDS选项,要求字段之 间使用逗号“,”间隔,所有字符类型字段值用双引号括起来:
1
|
mysqldump -uroot -p -T "/var/lib/mysql-files/" atguigu account --fields-terminatedby=',' --fields-optionally-enclosed-by='\"'
|
语句mysqldump语句执行成功之后,指定目录下会出现两个文件account.sql和account.txt。
打开account.sql文件,其内容包含创建account表的CREATE语句。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
[root@node1 mysql-files]# cat account.sql
-- MySQL dump 10.13 Distrib 8.0.26, for Linux (x86_64)
--
-- Host: localhost Database: atguigu
-- ------------------------------------------------------
-- Server version 8.0.26
/*!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' */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `account`
--
DROP TABLE IF EXISTS `account`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `account` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`balance` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!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 2022-01-07 23:36:39
|
打开account.txt文件,其内容包含创建account表的数据。从文件中可以看出,字段之间用逗号隔开,字 符类型的值被双引号括起来。
1
2
3
4
|
[root@node1 mysql-files]# cat account.txt
1,"张三",90
2,"李四",100
3,"王五",0
|
3. 使用mysql命令导出文本文件
**举例1:**使用mysql语句导出atguigu数据中account表中的记录到文本文件:
1
|
mysql -uroot -p --execute="SELECT * FROM account;" atguigu> "/var/lib/mysql-files/account.txt"
|
打开account.txt文件,其内容包含创建account表的数据。
1
2
3
4
5
|
[root@node1 mysql-files]# cat account.txt
id name balance
1 张三 90
2 李四 100
3 王五 0
|
**举例2:**将atguigu数据库account表中的记录导出到文本文件,使用–veritcal参数将该条件记录分为多行显示:
1
|
mysql -uroot -p --vertical --execute="SELECT * FROM account;" atguigu > "/var/lib/mysql-files/account_1.txt"
|
打开account_1.txt文件,其内容包含创建account表的数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
[root@node1 mysql-files]# cat account_1.txt
*************************** 1. row ***************************
id: 1
name: 张三
balance: 90
*************************** 2. row ***************************
id: 2
name: 李四
balance: 100
*************************** 3. row ***************************
id: 3
name: 王五
balance: 0
|
**举例3:**将atguigu数据库account表中的记录导出到xml文件,使用–xml参数,具体语句如下。
1
|
mysql -uroot -p --xml --execute="SELECT * FROM account;" atguigu>"/var/lib/mysqlfiles/account_3.xml"
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
[root@node1 mysql-files]# cat account_3.xml
<?xml version="1.0"?>
<resultset statement="SELECT * FROM account"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<row>
<field name="id">1</field>
<field name="name">张三</field>
<field name="balance">90</field>
</row>
<row>
<field name="id">2</field>
<field name="name">李四</field>
<field name="balance">100</field>
</row>
<row>
<field name="id">3</field>
<field name="name">王五</field>
<field name="balance">0</field>
</row>
</resultset>
|
说明:如果要将表数据导出到html文件中,可以使用 --html
选项。然后可以使用浏览器打开。
6.2 表的导入
1. 使用LOAD DATA INFILE方式导入文本文件
举例1:
使用SELECT…INTO OUTFILE将atguigu数据库中account表的记录导出到文本文件
1
|
SELECT * FROM atguigu.account INTO OUTFILE '/var/lib/mysql-files/account_0.txt';
|
删除account表中的数据:
1
|
DELETE FROM atguigu.account;
|
从文本文件account.txt中恢复数据:
1
|
LOAD DATA INFILE '/var/lib/mysql-files/account_0.txt' INTO TABLE atguigu.account;
|
查询account表中的数据:
1
2
3
4
5
6
7
8
9
|
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 90 |
| 2 | 李四 | 100 |
| 3 | 王五 | 0 |
+----+--------+---------+
3 rows in set (0.00 sec)
|
举例2: 选择数据库atguigu,使用SELECT…INTO OUTFILE将atguigu数据库account表中的记录导出到文本文件,使用FIELDS选项和LINES选项,要求字段之间使用逗号",“间隔,所有字段值用双引号括起来:
1
|
SELECT * FROM atguigu.account INTO OUTFILE '/var/lib/mysql-files/account_1.txt' FIELDS TERMINATED BY ',' ENCLOSED BY '\"';
|
删除account表中的数据:
1
|
DELETE FROM atguigu.account;
|
从/var/lib/mysql-files/account.txt中导入数据到account表中:
1
|
LOAD DATA INFILE '/var/lib/mysql-files/account_1.txt' INTO TABLE atguigu.account FIELDS TERMINATED BY ',' ENCLOSED BY '\"';
|
查询account表中的数据,具体SQL如下:
1
2
3
4
5
6
7
8
9
10
|
select * from account;
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 90 |
| 2 | 李四 | 100 |
| 3 | 王五 | 0 |
+----+--------+---------+
3 rows in set (0.00 sec)
|
2. 使用mysqlimport方式导入文本文件
举例:
导出文件account.txt,字段之间使用逗号”,“间隔,字段值用双引号括起来:
1
|
SELECT * FROM atguigu.account INTO OUTFILE '/var/lib/mysql-files/account.txt' FIELDS TERMINATED BY ',' ENCLOSED BY '\"';
|
删除account表中的数据:
1
|
DELETE FROM atguigu.account;
|
使用mysqlimport命令将account.txt文件内容导入到数据库atguigu的account表中:
1
|
mysqlimport -uroot -p atguigu '/var/lib/mysql-files/account.txt' --fields-terminated-by=',' --fields-optionally-enclosed-by='\"'
|
查询account表中的数据:
1
2
3
4
5
6
7
8
9
10
|
select * from account;
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 90 |
| 2 | 李四 | 100 |
| 3 | 王五 | 0 |
+----+--------+---------+
3 rows in set (0.00 sec)
|
7. 数据库迁移
7.1 概述
数据迁移(data migration)是指选择、准备、提取和转换数据,并将数据从一个计算机存储系统永久地传输到另一个计算机存储系统的过程。此外, 验证迁移数据的完整性
和 退役原来旧的数据存储
,也被认为是整个数据迁移过程的一部分。
数据库迁移的原因是多样的,包括服务器或存储设备更换、维护或升级,应用程序迁移,网站集成,灾难恢复和数据中心迁移。
根据不同的需求可能要采取不同的迁移方案,但总体来讲,MySQL 数据迁移方案大致可以分为物理迁移
和 逻辑迁移
两类。通常以尽可能 自动化
的方式执行,从而将人力资源从繁琐的任务中解放出来。
7.2 迁移方案
物理迁移适用于大数据量下的整体迁移。使用物理迁移方案的优点是比较快速,但需要停机迁移并且要 求 MySQL 版本及配置必须和原服务器相同,也可能引起未知问题。
物理迁移包括拷贝数据文件和使用 XtraBackup 备份工具两种。
不同服务器之间可以采用物理迁移,我们可以在新的服务器上安装好同版本的数据库软件,创建好相同目录,建议配置文件也要和原数据库相同,然后从原数据库方拷贝来数据文件及日志文件,配置好文件组权限,之后在新服务器这边使用 mysqld 命令启动数据库。
逻辑迁移适用范围更广,无论是 部分迁移
还是 全量迁移
,都可以使用逻辑迁移。逻辑迁移中使用最多的就是通过 mysqldump 等备份工具。
7.3 迁移注意点
1. 相同版本的数据库之间迁移注意点
指的是在主版本号相同的MySQL数据库之间进行数据库移动。
方式1
: 因为迁移前后MySQL数据库的 主版本号相同
,所以可以通过复制数据库目录来实现数据库迁移,但是物理迁移方式只适用于MyISAM引擎的表。对于InnoDB表,不能用直接复制文件的方式备份数据库。
方式2
: 最常见和最安全的方式是使用 mysqldump命令
导出数据,然后在目标数据库服务器中使用 MySQL命令导入。
举例:
1
2
3
|
#host1的机器中备份所有数据库,并将数据库迁移到名为host2的机器上
mysqldump –h host1 –uroot –p –-all-databases|
mysql –h host2 –uroot –p
|
在上述语句中,“|”符号表示管道,其作用是将mysqldump备份的文件给mysql命令;“–all-databases”表示要迁移所有的数据库。通过这种方式可以直接实现迁移。
2. 不同版本的数据库之间迁移注意点
例如,原来很多服务器使用5.7版本的MySQL数据库,在8.0版本推出来以后,改进了5.7版本的很多缺陷, 因此需要把数据库升级到8.0版本
旧版本与新版本的MySQL可能使用不同的默认字符集,例如有的旧版本中使用latin1作为默认字符集,而最新版本的MySQL默认字符集为utf8mb4。如果数据库中有中文数据,那么迁移过程中需要对 默认字符集
进行修改 ,不然可能无法正常显示数据。
高版本的MySQL数据库通常都会 兼容低版本
,因此可以从低版本的MySQL数据库迁移到高版本的MySQL 数据库。
3. 不同数据库之间迁移注意点
不同数据库之间迁移是指从其他类型的数据库迁移到MySQL数据库,或者从MySQL数据库迁移到其他类 型的数据库。这种迁移没有普适的解决方法。
迁移之前,需要了解不同数据库的架构, 比较它们之间的差异
。不同数据库中定义相同类型的数据的 关键字可能会不同
。例如,MySQL中日期字段分为DATE和TIME两种,而ORACLE日期字段只有DATE;SQL Server数据库中有ntext、Image等数据类型,MySQL数据库没有这些数据类型;MySQL支持的ENUM和SET 类型,这些SQL Server数据库不支持。
另外,数据库厂商并没有完全按照SQL标准来设计数据库系统,导致不同的数据库系统的 SQL语句
有差别。例如,微软的SQL Server软件使用的是T-SQL语句,T-SQL中包含了非标准的SQL语句,不能和MySQL的SQL语句兼容。
不同类型数据库之间的差异造成了互相 迁移的困难
,这些差异其实是商业公司故意造成的技术壁垒。但 是不同类型的数据库之间的迁移并 不是完全不可能
。例如,可以使用 MyODBC
实现MySQL和SQL Server之 间的迁移。MySQL官方提供的工具 MySQL Migration Toolkit
也可以在不同数据之间进行数据迁移。 MySQL迁移到Oracle时,需要使用mysqldump命令导出sql文件,然后, 手动更改
sql文件中的CREATE语句。
7.4 迁移小结
8. 删库了不敢跑,能干点啥?
8.1 delete:误删行
8.2 truncate/drop :误删库/表
8.3 预防使用truncate/drop误删库/表
8.4 rm:误删MySQL实例
对于一个有高可用机制的MySQL集群来说,不用担心 rm删除数据 了。只是删掉了其中某一个节点的数据的话,HA系统就会开始工作,选出一个新的主库,从而保证整个集群的正常工作。我们要做的就是在这个节点上把数据恢复回来,再接入整个集群。
但如果是恶意地把整个集群删除,那就需要考虑跨机房备份,跨城市备份。