复制
复制(replication)功能可以将一个MySQL数据库服务器(主库)中的数据复制到一个或多个MySQL数据库服务器(从库)。默认情况下,复制是异步的;从库不需要永久连接以接收来自主库的更新。可以将其配置为复制所有数据库、复制指定的数据库,甚至可以配置为复制数据库中指定的表。
简单的说,复制的原理如下:在服务器(主库)上执行的所有DDL和DML语句都会被记录到二进制日志中,这些日志由连接到它的服务器(从库)提取。它们只是被复制到从库,并被保存为中继日志。这个过程由一个称为IO线程的线程负责。还有一个线程叫做SQL线程,它按顺序执行中继日志中的语句。
详细复制过程:
- Slave上面的IO线程连接上Master,并请求从指定Binary log文件的指定位置(或者从最开始的日志)之后的日志内容;
- Master接收到来自Slave的IO线程的请求后,通过负责复制的IO线程根据请求信息读取指定日志指定位置之后的日志信息,返回给Slave端的IO线程。返回信息中除了日志所包含的信息之外,还包括本次返回的信息在Master端Binary log文件的名称以及在Binary log中的位置;
- Slave的IO线程收到信息后,将接收到的日志内容依次写入到Slave端的RelayLog文件(mysql-relay-lin.xxxxx)的最末端,并将读取到的Master端的bin-log的文件名和位置记录到master-info文件中,以便在下一次读取的时候能够清楚的告诉master“我需要从某个bin-log的哪个位置开始往后的日志内容,请发给我”;
- Slave的SQL线程检测到Relay Log中新增加了内容后,会马上解析该Log文件中的内容成为在Master端真实执行时候的那些可执行的查询或操作语句,并在自身执行那些查询或操作语句,这样,实际上就是在master端和Slave端执行了同样的查询或操作语句,所以两端的数据是完全一样的。
三个线程
对于每一个主从复制的连接,都有三个线程。拥有多个从库的主库为每一个连接到主库的从库创建一个binlog输出线程,每一个从库都有它自己的I/O线程和SQL线程。
- binlog输出线程:每当有从库连接到主库的时候,主库都会创建一个线程然后发送binlog内容到从库。对于每一个即将发送给从库的sql事件,binlog输出线程会将其锁住。一旦该事件被线程读取完之后,该锁会被释放,即使在该事件完全发送到从库的时候,该锁也会被释放。
- 从库I/O线程:当START SLAVE语句在从库开始执行之后,从库创建一个I/O线程,该线程连接到主库并请求主库发送binlog里面的更新记录到从库上。
从库I/O线程读取主库的binlog输出线程发送的更新并拷贝这些更新到本地文件,其中包括relay log文件。 - 从库的SQL线程:从库创建一个SQL线程,这个线程读取从库I/O线程写到relay log的更新事件并执行。
复制有如下优点:
- 水平解决方案:将负载分散到多个从库以提供性能。在此环境中,所有的写入和更新都必须在主库上进行。但是,读操作可能发生在一个或多个从库上。该模式可以提高写入的性能(因为主库专门用于更新),同时对于不断增加的从库也能显著加快其读取速度。
- 数据安全性:因为数据被复制到从库,而且从库可以暂停复制过程,所以可以在从库上运行备份服务而不会损坏相应的主库数据。
- 分析:在主库上可以实时创建数据,而对信息的分析可以在从库上进行,不会影响主库的性能。
- 远程数据分发:你可以使用复制为远程服务器站点创建本地数据的副本,无需永久访问主库。
10.1 准备复制
复制有许多拓扑形式。其中一些是传统的主从复制、链式复制、主主复制、多源复制等等。
传统复制:涉及单个主库和多个从库,如下图所示:
链式复制:意味着一台服务器从另一台复制,而另一台服务器又从另一台复制。中间服务器称为中继主库(主库–>中继主库–>从库):
如果想在两个数据中心之间设置复制,一般会使用这种方式。主库(the primary master)及其从库将位于一个数据中心内。辅助主库(中继主库)从另一个数据中心的主库进行复制。另一个数据中心的所有从库都从辅助主库(the secondary master)复制。
主主复制:在此拓扑中,两个主库互相之间都可以接受写入和复制,如图所示:
多源复制:在这种拓扑中,一个从库将从多个主库而非从一个主库复制,如图所示:
操作过程
大致方案如下:
- 在主库上启用二进制日志记录
- 在主库上创建一个复制用户
- 在从库上设置唯一的server_id
- 从主库中取得备份
- 恢复从库上的备份
- 执行CHANGE MASTER TO命令
- 开始复制
10.2 主从复制案例环境:
- master: 192.168.154.136
- slave01: 192.168.154.138
前提条件:
- 主库和从库的数据库的版本保持一致;
- 主从复制集群中每个数据库实例的 server-id 的值不能重复;
- 要开启归档日志并且归档日志的格式选择为 row 方式;
mysql> show variables like '%binlog_format%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW |
+---------------+-------+
1 row in set (0.03 sec)
具体步骤如下:
1. 在主库上,启用二进制日志记录并设置SERVER_ID。
[root@master ~]# vim /etc/my.cnf
[mysqld]
server_id=136 #设置主服务器server_id
log_bin=/var/lib/mysql/binlogs/master
log_bin_index=/var/lib/mysql/binlogs/master.index
#重启服务
[root@master mysql]# systemctl restart mysqld.service
#导入样例库后,查看主库当前binary log文件名和位置(position)
mysql> show master status;
+---------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+---------------+----------+--------------+------------------+-------------------+
| master.000004 | 66378594 | | | |
+---------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
2. 在主库上,创建一个复制用户,从库使用此账号连接到主库:
mysql> create user 'binlog_user'@'%' identified with mysql_native_password by 'Com.123456';
Query OK, 0 rows affected (0.00 sec)
mysql> grant replication slave on *.* to 'binlog_user'@'%';
Query OK, 0 rows affected (0.01 sec)
#为了可以在从库登录主库,放行防火墙
[root@master ~]# firewall-cmd --add-service=mysql --permanent
success
[root@master ~]# firewall-cmd --reload
3. 在从库上,设置唯一的SERVER_ID选项(要与主库上设置的不同):
[root@slave01 ~]# vim /etc/my.cnf
[mysqld]
server_id=138
log_bin=/var/lib/mysql/binlogs/slave01
log_bin_index=/var/lib/mysql/binlogs/slave01.index
#重启服务
[root@slave01 ~]# systemctl restart mysqld
4. 在从库上,通过远程连接从主库进行备份。可以使用mysqldump或mydumper,不能使用mysqlpump,因为二进制日志的位置不一致。
#主库授权
mysql> create user 'root'@'192.168.154.%' identified with mysql_native_password by 'Com.123456';
Query OK, 0 rows affected (0.02 sec)
mysql> grant all on *.* to 'root'@'192.168.154.%';
Query OK, 0 rows affected (0.01 sec)
# 在从库进行备份
[root@slave01 ~]# mysqldump -h 192.168.154.136 -uroot --password=Com.123456 \
> --all-databases --routines --events --single-transaction --master-data > dump.sql
[root@slave01 ~]# ls -lh dump.sql
-rw-r--r--. 1 root root 164M 1月 2 17:11 dump.sql
从另一个从库备份时,必须设置–dump-slave选项
5. 在从库上,待备份完成后恢复此备份。
[root@slave01 ~]# mysql -uroot -pCom.123456 < dump.sql
6. 在从库上,恢复备份后,必须执行以下命令:
mysql> change master to master_host='192.168.154.136',
-> master_user='binlog_user',master_password='Com.123456',
-> master_log_file='master.000009', master_log_pos=679
-> ;
Query OK, 0 rows affected, 2 warnings (0.01 sec)
# 在从库上,执行start slave命令:
mysql> start slave;
Query OK, 0 rows affected (0.01 sec)
7. 查看复制的状态
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.154.136
Master_User: binlog_user
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: master.000009
Read_Master_Log_Pos: 1677
Relay_Log_File: slave01-relay-bin.000003
Relay_Log_Pos: 1317
Relay_Master_Log_File: master.000009
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 1677
Relay_Log_Space: 1527
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 136
Master_UUID: 609a58c5-27c8-11ea-96c1-000c29d09df1
Master_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 0
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
Master_public_key_path:
Get_master_public_key: 0
Network_Namespace:
1 row in set (0.00 sec)
Seconds_Behind_Master:其值代表的是复制的延迟情况。如果它的值为0,则意味着从库和主库同步;如果为非0值,则表示延迟的秒数;如果为NULL,则表示未复制。
如果出现server_uuid错误:(比如克隆虚拟机做实验)
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.
解决方法:
#进入主库或从库的数据目录:
[root@slave01 mysql]# cat auto.cnf
[auto]
server-uuid=56ac7ddc-2d46-11ea-bab5-000c294a081d
# 将其改名并重启服务即可生成新的server-uuid
[root@slave01 mysql]# mv auto.cnf auto.cnf.bak
[root@slave01 mysql]# systemctl restart mysqld
8. 验证同步情况
#在主库建立新数据库,并创建表,插入数据
mysql> create database banks;
Query OK, 1 row affected (0.01 sec)
mysql> use banks;
Database changed
mysql> create table account(name varchar(20),amount float);
Query OK, 0 rows affected (0.01 sec)
mysql> insert into account values ('zhangsan',50000);
Query OK, 1 row affected (0.01 sec)
mysql> insert into account values ('lisi',60000);
Query OK, 1 row affected (0.00 sec)
# 在从库查看是否同步数据
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| banks |
| employees |
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
6 rows in set (0.00 sec)
mysql> select * from banks.account;
+----------+--------+
| name | amount |
+----------+--------+
| zhangsan | 50000 |
| lisi | 60000 |
+----------+--------+
2 rows in set (0.00 sec)
10.3 设置主主复制
假设主库是master1和master2,具体步骤如下:
1. 按照此方法,在master1和master2之间设置复制;
2. 设置master2为只读:
mysql> set @@global.read_only=on;
3. 在master2上,检查当前二进制日志的坐标;
mysql> show master status;
+---------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+---------------+----------+--------------+------------------+-------------------+
| master.000009 | 1677 | | | |
+---------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
根据上面的信息,可以从位置为1677的master.000009文件处开始在master1上复制。
4. 从第三步中获取的位置开始,在master1上执行change master to命令:
mysql> change master to master_host='<master2_host>',
> master_user='binlog_user', master_password='binlog_P@ss12',
> master_log_file='<log_file_name>', master_log_pos=<position>
5. 在master1上开启slave模式:
mysql> start slave;
6. 最后,设置master2为‘可读/写’;
mysql> set @@global.read_only=off;
10.4 设置复制筛选器
可以选择要复制哪些表或数据库。
在主库上,可以使用–binlog-do-db和–binlog-ignore-db选项来选择要记录变更的数据库,以便控制二进制日志;
但更好的方法是控制从库,可以使用–replicate-*选项或通过创建复制筛选器来动态执行和忽略从主库收到的语句。
要创建筛选器,需要执行change replication filter语句。
仅复制数据库
假设只想复制db1和db2,可以使用以下语句来创建复制筛选器:
mysql> change replication filter replicate_do_db = (db1, db2);
# 注意,应该在括号内指定要复制的所有数据库;
复制特定的表
可以使用replicate_do_table指定要复制的表:
mysql> change replication filter replicate_do_table = ('db1.table1');
假设想使用正则表达式来选择表,可以使用replicate_wild_do_table选项:
mysql> change replication filter replicate_wild_do_table = ('db1.imp%');
忽略数据库
可以使用replicate_ignore_db指定你不想复制的数据库:
mysql> change replication filter replicate_ignore_db = (db1, db2);
忽略特定的表
可以使用replicate_ignore_table和replicate_wild_ignore_table选项忽略某些表。前者只接受完整的表名,后者可以允许使用通配符。
mysql> change replication filter replicate_ignore_table = ('db1.table1');
mysql> change replication filter replicate_wild_ignore_table=('db1.new%','db2.new%');
10.5 设置GTID复制
从 MySQL 5.6.5 开始新增了一种基于 GTID 的复制方式。通过 GTID 保证了每个在主库上提交的事务在集群中有一个唯一的ID。这种方式强化了数据库的主备一致性,故障恢复以及容错能力。
在原来基于二进制日志的复制中,从库需要告知主库要从哪个偏移量进行增量同步,如果指定错误会造成数据的遗漏,从而造成数据的不一致。借助GTID,在发生主备切换的情况下,MySQL的其它从库可以自动在新主库上找到正确的复制位置,这大大简化了复杂复制拓扑下集群的维护,也减少了人为设置复制位置发生误操作的风险。另外,基于GTID的复制可以忽略已经执行过的事务,减少了数据发生不一致的风险。
什么是GTID
全局事务标识符(Global Transaction Identifier,GTID)是在程序中创建的唯一标识符,并与主库上提交的每个事务相关联。此标识符是惟一的,不仅在其主库上,在给定的复制设置中的所有数据库上,它都是唯一的。所有事务和所有GTID之间都是一对一的映射关系。
GTID用一对坐标表示,用冒号分隔:
#格式:
GTID = source_id:transaction_id
#具体实例:
609a58c5-27c8-11ea-96c1-000c29d09df1:1
#连续的事务:
609a58c5-27c8-11ea-96c1-000c29d09df1:1-5
#GTID集合可以包含来自多个MySQL实例的事务,它们之间用逗号分隔。
#如果是来自同一MySQL实例的事务序号有多个范围区间,各组范围之间用冒号分隔:
609a58c5-27c8-11ea-96c1-000c29d09df1:1-5:11-18,609a58c5-27c8-11ea-96c1-000c29d09675:1-27
source_id是主库的标识。通常,服务器的server_uuid选项就代表此标识。transaction_id是一个序列号,由在该服务器上提交事务的顺序决定。例如,提交的第一个事务,transaction_id为1;在同一个主库上提交的第10个事务的transaction_id为10。
使用基于GTID的复制,MySQL会使用GTID自动检测二进制日志的位置。
GTID操作:默认情况下将一个事务记录进二进制文件时,首先记录它的 GTID,而且 GTID 和事务相关信息一并要发送给从服务器,由从服务器在本地应用认证,但是绝对不会改变原来的事务 ID 号。因此在 GTID 的架构上就算有了N层架构,复制是N级架构,事务 ID 依然不会改变,有效的保证了数据的完整和安全性。
GTID的优势:
- 根据 GTID 可以知道事务最初是在哪个实例上提交的
- GTID 的存在方便了 Replication 的 Failover(故障转移)
配置MySQL基于GTID的复制
GTID主从复制的配置思路
如果已在服务器之间设置过复制,请按照下列步骤操作:
1. 在my.cnf中启用GTID:
#MySQL默认设置:
mysql> show variables like '%gtid%';
+----------------------------------+-----------+
| Variable_name | Value |
+----------------------------------+-----------+
| binlog_gtid_simple_recovery | ON |
| enforce_gtid_consistency | OFF |
| gtid_executed | |
| gtid_executed_compression_period | 1000 |
| gtid_mode | OFF |
| gtid_next | AUTOMATIC |
| gtid_owned | |
| gtid_purged | |
| session_track_gtids | OFF |
+----------------------------------+-----------+
9 rows in set (0.00 sec)
#修改主配置文件(在主库上):
[root@master ~]# vim /etc/my.cnf
gtid_mode=ON
enforce_gtid_consistency=true
#修改主配置文件(在从库上):
[root@slave01 ~]# vim /etc/my.cnf
gtid_mode=ON
enforce_gtid_consistency=true
skip_slave_start
2. 将主库设置为只读,并确保所有从库都能与主库同步。这一点很重要,因为主库和从库之间不应该有任何数据不一致:
#主库设置只读
mysql> set @@global.read_only = ON;
Query OK, 0 rows affected (0.00 sec)
3. 重启所有从库使GTID生效。
[root@slave01 ~]# systemctl restart mysqld
由于在配置文件中给出了skip_slave_start,所以只有在执行了start slave命令之后,从库才会启动。如果启动从库,它将因为下面的错误而启动失败:
Last_IO_Error: The replication receiver thread cannot start because the master has GTID_MODE = OFF and this server has GTID_MODE = ON.
4. 重新启动主库。当重新启动主库时,它将以读/写模式开始运行,并开始接受以GTID模式写入:
[root@master ~]# systemctl restart mysqld.service
5. 执行change master to 命令来设置GTID复制:
mysql> change master to master_host='192.168.154.136',
-> master_port=3306,master_user='binlog_user',
-> master_password='Com.123456',master_auto_position = 1;
#master_auto_position 会自动找到执行的GTID。
6. 在所有从库上执行start slave:
mysql> start slave;
Query OK, 0 rows affected (0.00 sec)
7. 确认从库正在复制:
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.154.136
Master_User: binlog_user
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: master.000012
Read_Master_Log_Pos: 155
Relay_Log_File: slave01-relay-bin.000002
Relay_Log_Pos: 363
Relay_Master_Log_File: master.000012
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 155
Relay_Log_Space: 573
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 136
Master_UUID: 609a58c5-27c8-11ea-96c1-000c29d09df1
Master_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 1
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
Master_public_key_path:
Get_master_public_key: 0
Network_Namespace:
1 row in set (0.00 sec)
8. 在主库进行数据写入,进行同步验证
#主库上操作
mysql> create database huawei;
Query OK, 1 row affected (0.00 sec)
mysql> show master status;
+---------------+----------+--------------+------------------+----------------------------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+---------------+----------+--------------+------------------+----------------------------------------+
| master.000012 | 346 | | | 609a58c5-27c8-11ea-96c1-000c29d09df1:1 |
+---------------+----------+--------------+------------------+----------------------------------------+
1 row in set (0.00 sec)
#在从库上验证:
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.154.136
Master_User: binlog_user
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: master.000012
Read_Master_Log_Pos: 346
Relay_Log_File: slave01-relay-bin.000002
Relay_Log_Pos: 554
Relay_Master_Log_File: master.000012
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 346
Relay_Log_Space: 764
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 136
Master_UUID: 609a58c5-27c8-11ea-96c1-000c29d09df1
Master_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set: 609a58c5-27c8-11ea-96c1-000c29d09df1:1
Executed_Gtid_Set: 609a58c5-27c8-11ea-96c1-000c29d09df1:1
Auto_Position: 1
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
Master_public_key_path:
Get_master_public_key: 0
Network_Namespace:
1 row in set (0.00 sec)