一、MySQL GTID Replication
MySQL 5.6的新特性之一,加入了全局事务ID (Global Transaction ID) 来强化数据库的主备一致性,故障恢复,以及容错能力。用于取代过去通过 binlog 文件偏移量定位复制位置的传统方式。MySQL 会为每一个 DML/DDL 操作增加一个唯一标记叫做 GTID,这个标记在整个复制环境中都是唯一的。主从环境中主库的 dump 线程可以直接通过 GTID 定位到需要发送的 binary log 位置,而不再需要指定 binary log 的文件名和位置,因此切换极为方便。
GTID 长什么样?
根据官方文档定义,GTID 由 source_id 加 transaction_id 构成。
GTID = source_id:transaction_id
上面的 source_id 指发起事务的 MySQL 实例,值为该实例的 server_uuid。server_uuid 由 MySQL 在第一次启动时自动生成并被持久化到 auto.cnf 文件里,TID(transaction_id)是一个从 1 开始的自增计数,表示在这个主库上执行的第 n 个事务。MySQL 会保证事务与 GTID 之间的 1:1 映射。例如:
e6954592-8dba-11e6-af0e-fa163e1cf111:1
表示在以 “e6954592-8dba-11e6-af0e-fa163e1cf111” 为唯一标示的 MySQL 实例上执行的第 1 个数据库事务。很容易理解,MySQL 只要保证每台数据库的 server_uuid 全局唯一,以及每台数据库生成的 transaction_id 自身唯一,就能保证 GTID 的全局唯一性。
GTID 的集合(SET),可以包含多个 server_uuid,比如我们常见的 execute_gtid、gtid_purged 就是一个 GTID 集合。例如:
e6954592-8dba-11e6-af0e-fa163e1cf111:1-5:7-10
GTID 集合中某个 server_uuid 可能包含多个区间,比如 1-5:7-10,这里就有两个 GTID SET Interval 分别是 1-5 和 7-10。当然,通常只有一个 GTID SET Interval,如 1-10。
可以使用 show master status 实时看当前的事务执行数。
server_uuid 的生成?
MySQL 5.6 用 128 位的 server_uuid 代替了原本的 32 位 server-id 的大部分功能。原因很简单,server-id 依赖于 my.cnf 的手工配置,有可能产生冲突 —— 而自动产生 128 位 uuid 的算法可以保证所有的 MySQL uuid 都不会冲突。
MySQL 5.6 在数据目录下有一个 auto.cnf 文件就是用来保存 server_uuid 的,如下:
$ cat /mydata/auto.cnf
[auto]
server-uuid=2ccfb36b-7bb3-11e5-b426-525400c14962
在 MySQL 再次启动时会读取 auto.cnf 文件,继续使用上次生成的 server_uuid。使用 SHOW 命令可以查看 MySQL 实例当前使用的 server_uuid:SHOW GLOBAL VARIABLES LIKE ‘server_uuid’; 它是一个 MySQL 5.6 的 global variables。
Tips:如果 auto.cnf 丢失,则会调用 generate_server_uuid 函数生成一个新的 server_uuid,但是需要注意这样 GTID 必然会发生改变。
GTID 生成时间?
GTID 是在 ‘commit’ 命令发起后,order commit 执行到 flush 阶段需要生成 GTID Event 的时候才会获取。MySQL 内部维护了一个全局的 GTID 的计数器 next_free_gno 用于生成 gno。可参考 Gtid_state::get_automatic_gno 函数。
gtid_executed 表的作用?
MySQL 5.6 版本开启基于 GTID 的复制模式时,从库必须打开 binary log 和 log_slave_updates,也就是必须在从机上再记录一份二进制日志。这样的无论对性能还是存储的开销,无疑会相应的增大。而 MySQL 5.7 版本开始无需在 GTID 模式下启用参数 log_slave_updates,其中最重要的原因在于 MySQL 5.7 在 mysql 库下引入了新的表 gtid_executed,是用来持久化 GTID 的一个介质。
其表结构如下所示:
mysql> SHOW CREATE TABLE mysql.gtid_executed\G
*************************** 1. row ***************************
Table: gtid_executed
Create Table: CREATE TABLE `gtid_executed` (
`source_uuid` char(36) NOT NULL COMMENT 'uuid of the source where the transaction was originally executed.',
`interval_start` bigint(20) NOT NULL COMMENT 'First number of interval.',
`interval_end` bigint(20) NOT NULL COMMENT 'Last number of interval.',
PRIMARY KEY (`source_uuid`,`interval_start`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 STATS_PERSISTENT=0
1 row in set (0.00 sec)
该表是一个 InnoDB 表,该表会记录当前执行的 GTID。列 source 对应 UUID,列 interval_start/interval_end 表示的是事务号。在 MySQL 5.6 中必须配置参数 log_slave_updates 的最重要原因在于当 slave 重启后,无法得知当前 slave 已经运行到的 GTID 位置,因为变量 gtid_executed 是一个内存值:
mysql> select @@global.gtid_executed\G
*************************** 1. row ***************************
@@global.gtid_executed: 7af7d3ea-933b-11e5-9da7-fa163e30f9a2:1-72054
1 row in set (0.00 sec)
所以 MySQL 5.6 的处理方法就是启动时扫描最后一个二进制日志,获取当前执行到的 GTID 位置信息,二进制日志中每个事务的 GTID_LOG_EVENT 事件记录了 GTID 值。当然,如果DBA不小心将二进制日志删除了,那么这又会带来灾难性的问题。
因此,MySQL 5.7 将 gtid_executed 这个值给持久化了。采用的技巧与 MySQL 5.6 处理 SQL thread 保存位置的方式一样,即将 GTID 值持久化保存在一张 InnoDB 表中,并与用户事务一起进行提交,从而实现数据的一致性:
START TRANSACTION;
# user statement
......
INSERT INTO mysql.gtid_executed VALUES (...)
END;
需要注意的是表 mysql.gtid_executed 是在主服务器和从服务器上都进行更新的,而表 slave_relay_log_info 仅在从服务器上更新。
MySQL 5.7 对于表 mysql.gtid_executed 的更新策略也有些不同,如果没有主服务器没有开启 log_bin 或者从服务器没有开启 log_slave_updates,其会每一个事务更新表 gtid_executed,这样服务器重启后可以快速知道当前服务器执行到的 GTID 位置。因此,用户可能在服务器上看到类似如下的内容:
mysql> select * from mysql.gtid_executed;
+--------------------------------------+----------------+--------------+
| source_uuid | interval_start | interval_end |
+--------------------------------------+----------------+--------------+
| 05e16691-bf69-11e5-97cf-fa163e30f9a2 | 1 | 4334294 |
| 05e16691-bf69-11e5-97cf-fa163e30f9a2 | 4334296 | 4352984 |
| 05e16691-bf69-11e5-97cf-fa163e30f9a2 | 4352985 | 4352985 |
| 05e16691-bf69-11e5-97cf-fa163e30f9a2 | 4352986 | 4352986 |
| 05e16691-bf69-11e5-97cf-fa163e30f9a2 | 4352987 | 4352987 |
| 05e16691-bf69-11e5-97cf-fa163e30f9a2 | 4352988 | 4352988 |
......
那这样岂不是表 gtid_executed 中的记录会疯狂增长。为此,MySQL 5.7 又引入了新的线程,用来对此表进行压缩,该线程如下所示:
mysql> select thread_id,thread_os_id,name,processlist_command,processlist_state from threads where name like '%compress%'\G
*************************** 1. row ***************************
thread_id: 39
thread_os_id: 23816
name: thread/sql/compress_gtid_table
processlist_command: Daemon
processlist_state: Suspending
1 row in set (0.01 sec)
参数 gtid_executed_compression_period 用来控制每执行多少个事务,对此表进行压缩,默认值为 1000。因此,过一段时间后,上述的表 mysql.gtid_executed 会压缩成如下的内容:
mysql> select * from mysql.gtid_executed;
+--------------------------------------+----------------+--------------+
| source_uuid | interval_start | interval_end |
+--------------------------------------+----------------+--------------+
| 05e16691-bf69-11e5-97cf-fa163e30f9a2 | 1 | 4334294 |
| 05e16691-bf69-11e5-97cf-fa163e30f9a2 | 4334296 | 4354329 |
......
若 MySQL 服务器启用了二进制日志,则表 mysql.gtid_executed 的压缩仅在二进制 rotation 时发生,因为发生重启等情况依旧可以通过扫描二进制日志判断得知当前运行的 GTID 位置。
二、MySQL 基于 GTID 的主从复制
基于 GTID 的复制有什么优点?
GTID 的使用不单单是用单独的标识符替换旧的二进制日志文件/位置,它也采用了新的复制协议。旧的协议往往简单直接,即:首先从服务器上在一个特定的偏移量那里连接到一个给定的二进制日志文件,然后主服务器再从给定的连接点开始发送所有的事件。新协议稍有不同:支持以全局统一事务ID(GTID)为基础的复制。当在主库上提交事务或者被从库应用时,可以定位和追踪每一个事务。GTID复制是全部以事务为基础,使得检查主从一致性变得非常简单。如果所有主库上提交的事务也同样提交到从库上,一致性就得到了保证。
GTID相关操作:默认情况下将一个事务记录进二进制文件时,首先记录它的GTID,而且GTID和事务相关信息一并要发送给从服务器,由从服务器在本地应用认证,但是绝对不会改变原来的事务ID号。因此在GTID的架构上就算有了N层架构,复制是N级架构、事务ID依然不会改变,有效的保证了数据的完整和安全性。
你可以使用基于语句的或基于行的复制与GTIDs ,但是,为了获得最佳效果,我们建议你使用基于行(ROW)的格式。
另外支持启用GTID,对运维人员来说应该是一件令人高兴的事情,在配置主从复制,传统的方式里,你需要找到binlog和pos点,然后change master to指向,而不是很有经验的运维,往往会将其找错,造成主从同步复制报错,在MySQL 5.6里,如果使用了GTID,启动一个新的复制从库或切换到一个新的主库,就不必依赖log文件或者pos位。只需要知道master的IP、端口,账号密码即可,因为同步复制是自动的,mysql通过内部机制GTID自动找点同步。
1)在传统的复制里面,当发生故障,需要主从切换,需要找到binlog和pos点,然后change master to指向新的master,相对来说比较麻烦,也容易出错。在MySQL 5.6里面,不用再找binlog和pos点,我们只需要知道master的ip,端口,以及账号密码就行,因为复制是自动的,MySQL会通过内部机制GTID自动找点同步。
2)多线程复制(基于库),在MySQL 5.6以前的版本,slave的复制是单线程的。一个事件一个事件的读取应用。而master是并发写入的,所以延时是避免不了的。唯一有效的方法是把多个库放在多台slave,这样又有点浪费服务器。在MySQL 5.6里面,我们可以把多个表放在多个库,这样就可以使用多线程复制,当只有1个库,多线程复制是没有用的。
基于 GTID 复制实现的工作原理?
1)master更新数据时,会在事务前产生GTID,一同记录到binlog日志中。
2)slave端的i/o 线程将变更的binlog,写入到本地的relay log中。
3)sql线程从relay log中获取GTID,然后对比slave端的binlog是否有记录(所以MySQL5.6 SLAVE必须要开启二进制日志记录)。
4)如果有记录,说明该GTID的事务已经执行,slave会忽略。
5)如果没有记录,slave就会从relay log中执行该GTID的事务,并记录到binlog。
6)在解析过程中会判断是否有主键,如果没有就用二级索引,如果没有就用全部扫描。
2.1 实验环境配置
1)Master/Slave时间同步。
2)Master/Slave关闭防火墙和Selinux。
3)Master/Slave主机名设定。
4)Master/Slave安装MySQL5.7
看:MySQL 5.7多方式安装
5)Master/Slave准备标准目录
$ mkdir /data/mysql/3306/{data,log/{binlog,relaylog,slowlog},tmp,conf} -p
$ mkdir /data/mysql/3306/{data,log/{binlog,relaylog,slowlog},tmp,conf} -p
6)数据库初始化
$ mysqld --initialize-insecure --user=mysql --datadir=/data/mysql/3306/data/
$ mysqld --initialize-insecure --user=mysql --datadir=/data/mysql/3306/data/
2.2 MASTER节点(3306)
1)配置主配置文件
$ cat /data/mysql/3306/conf/my.cnf
[mysqld]
##########basic settings###################
port = 3306
bind-address = 0.0.0.0
datadir = /data/mysql/3306/data
socket = /data/mysql/3306/mysql.sock
pid-file = /data/mysql/3306/mysql.pid
server-id = 103306
user = mysql
character_set_server = utf8mb4
skip_name_resolve = 1
max_allowed_packet = 16777216
max_connections = 2000
max_connect_errors = 1000
tmpdir = /data/mysql/3306/tmp
tmp_table_size = 67108864
explicit_defaults_for_timestamp = 1
join_buffer_size = 134217728
interactive_timeout = 1800
wait_timeout = 1800
read_buffer_size = 16777216
read_rnd_buffer_size = 33554432
sort_buffer_size = 33554432
key_buffer_size = 256M
transaction_isolation = READ-COMMITTED
##########log settings###################
log-bin = /data/mysql/3306/log/binlog/mysql-bin
log_bin_index = /data/mysql/3306/log/binlog/mysql-bin.index
expire_logs_days = 30
binlog_format = ROW
log_error = /data/mysql/3306/log/error.log
slow_query_log = 1
long_query_time = 2
log_slow_admin_statements = 1
log_slow_slave_statements = 1
slow_query_log_file = /data/mysql/3306/log/slowlog/slow.log
min_examined_row_limit = 100
binlog-rows-query-log_events = 1
sync-binlog = 1
###########replication##########
gtid-mode = on
enforce-gtid-consistency = true
2)启动MASTER
$ nohup mysqld --defaults-file=/data/mysql/3306/conf/my.cnf &
3)查看GTID是否正常启用
$ mysql -S /data/mysql/3306/mysql.sock
mysql> create database blog;
Query OK, 1 row affected (0.00 sec)
mysql> create table blog.info(id int not null);
Query OK, 0 rows affected (0.02 sec)
mysql> show master status;
+------------------+----------+--------------+------------------+------------------------------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+------------------------------------------+
| mysql-bin.000003 | 486 | | | 6edc34c8-d23d-11e6-b440-fa163e2a6390:1-2 |
+------------------+----------+--------------+------------------+------------------------------------------+
1 row in set (0.00 sec)
查看GTID相关参数,可以通过MySQL的几个变量查看相关的GTID信息。
mysql> show global variables like '%gtid%';
+----------------------------------+------------------------------------------+
| Variable_name | Value |
+----------------------------------+------------------------------------------+
| binlog_gtid_simple_recovery | ON |
| enforce_gtid_consistency | ON |
| gtid_executed | 6edc34c8-d23d-11e6-b440-fa163e2a6390:1-2 |
| gtid_executed_compression_period | 1000 |
| gtid_mode | ON |
| gtid_owned | |
| gtid_purged | |
| session_track_gtids | OFF |
+----------------------------------+------------------------------------------+
8 rows in set (0.00 sec)
mysql> show variables like '%gtid_next%';
+---------------+-----------+
| Variable_name | Value |
+---------------+-----------+
| gtid_next | AUTOMATIC |
+---------------+-----------+
1 row in set (0.00 sec)
gtid_executed
在当前实例上执行过的GTID集合; 实际上包含了所有记录到binlog中的事务。所以,设置set sql_log_bin=0后执行的事务不会生成binlog 事件,也不会被记录到gtid_executed中。执行RESET MASTER可以将该变量置空。
gtid_purged
binlog不可能永远驻留在服务上,需要定期进行清理(通过expire_logs_days可以控制定期清理间隔),否则迟早它会把磁盘用尽。gtid_purged用于记录已经被清除了的binlog事务集合,它是gtid_executed的子集。只有gtid_executed为空时才能手动设置该变量,此时会同时更新gtid_executed为和gtid_purged相同的值。gtid_executed为空意味着要么之前没有启动过基于GTID的复制,要么执行过RESET MASTER。执行RESET MASTER时同样也会把gtid_purged置空,即始终保持gtid_purged是gtid_executed的子集。
gtid_next
会话级变量,指示如何产生下一个GTID。可能的取值如下:
第一个:AUTOMATIC
自动生成下一个GTID,实现上是分配一个当前实例上尚未执行过的序号最小的GTID。
第二个:ANONYMOUS
设置后执行事务不会产生GTID。
第三个:显式指定的GTID
可以指定任意形式合法的GTID值,但不能是当前gtid_executed中的已经包含的GTID,否则,下次执行事务时会报错。
参数:https://dev.mysql.com/doc/refman/5.7/en/replication-options-gtids.html
4)创建复制用户
mysql> CREATE USER 'mysql_slave'@'%' IDENTIFIED BY '123456';
mysql> GRANT REPLICATION SLAVE ON *.* TO 'mysql_slave'@'%';
mysql> flush privileges;
2.3 SLAVE节点(3306)
1)配置主配置文件
$ cat /data/mysql/3306/conf/my.cnf
[mysqld]
##########basic settings###################
port = 3306
bind-address = 0.0.0.0
datadir = /data/mysql/3306/data
socket = /data/mysql/3306/mysql.sock
pid-file = /data/mysql/3306/mysql.pid
user = mysql
server-id = 103307
character_set_server = utf8mb4
skip_name_resolve = 1
max_allowed_packet = 16777216
max_connections = 2000
max_connect_errors = 1000
tmpdir = /data/mysql/3306/tmp
tmp_table_size = 67108864
explicit_defaults_for_timestamp = 1
join_buffer_size = 134217728
interactive_timeout = 1800
wait_timeout = 1800
read_buffer_size = 16777216
read_rnd_buffer_size = 33554432
sort_buffer_size = 33554432
key_buffer_size = 256M
transaction_isolation = READ-COMMITTED
##########log settings###################
log-bin = /data/mysql/3306/log/binlog/mysql-bin
log_bin_index = /data/mysql/3306/log/binlog/mysql-bin.index
expire_logs_days = 30
binlog_format = ROW
log_error = /data/mysql/3307/log/error.log
slow_query_log = 1
long_query_time = 2
log_slow_admin_statements = 1
log_slow_slave_statements = 1
slow_query_log_file = /data/mysql/3306/log/slowlog/slow.log
min_examined_row_limit = 100
binlog-rows-query-log_events = 1
sync-binlog = 1
###########replication##########
gtid-mode = on
enforce-gtid-consistency = true
relay-log = /data/mysql/3306/log/relaylog/relay-log
log-slave-updates = true
master-info-repository = table
relay-log-info-repository = table
slave-parallel-workers = 1
binlog-checksum=CRC32
master-verify-checksum = 1
slave-sql-verify-checksum = 1
slave_allow_batching = 1
slave_skip_errors = ddl_exist_errors
slave_transaction_retries=128
relay_log_purge = 1
relay_log_recovery = 1
report-port = 3306
report-host = 10.99.73.10
skip-slave-start
2)启动SLAVE
$ nohup mysqld --defaults-file=/data/mysql/3306/conf/my.cnf &
3)连接MASTER
$ mysql -S /data/mysql/3306/mysql.sock
mysql> CHANGE MASTER TO MASTER_HOST='10.99.73.10',MASTER_PORT=3306,MASTER_USER='mysql_slave', MASTER_PASSWORD='123456', MASTER_AUTO_POSITION=1;
Query OK, 0 rows affected, 2 warnings (0.02 sec)
4)启动复制线程
mysql> start slave;
Query OK, 0 rows affected (0.01 sec)
2.4 验证主从复制
MASTER主机
$ mysql -S /data/mysql/3306/mysql.sock
mysql> show slave hosts;
+-----------+-------------+------+-----------+--------------------------------------+
| Server_id | Host | Port | Master_id | Slave_UUID |
+-----------+-------------+------+-----------+--------------------------------------+
| 103307 | 10.99.73.10 | 3306 | 103306 | 728dfb96-d23d-11e6-b9c1-fa163e2a6390 |
+-----------+-------------+------+-----------+--------------------------------------+
1 row in set (0.00 sec)
SLAVE主机
$ mysql -S /data/mysql/3306/mysql.sock
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 10.99.73.10
Master_User: mysql_slave
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000003
Read_Master_Log_Pos: 1105
Relay_Log_File: relay-log.000002
Relay_Log_Pos: 1318
Relay_Master_Log_File: mysql-bin.000003
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: 1105
Relay_Log_Space: 1519
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: 103306
Master_UUID: 6edc34c8-d23d-11e6-b440-fa163e2a6390
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: 6edc34c8-d23d-11e6-b440-fa163e2a6390:1-5
Executed_Gtid_Set: 6edc34c8-d23d-11e6-b440-fa163e2a6390:1-5
Auto_Position: 1
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
1 row in set (0.00 sec)
可以看到IO和SQL线程都为YES,另外retrieved_Gtid_Set接收了5个事务,Executed_Gtid_Set执行了5个事务。
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| blog |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)
主的数据库已经复制过来了。
三、对主从复制配置中定义的参数进行介绍
log-bin = mysql-bin
从服务器是否开启二进制日志,默认关闭。
binlog-format = row
启用基于行的二进制日志的记录,对于复制更容易校验,不容易出错。
log-slave-updates = 0 | 1
Slave更新操作是否记入二进制日志,如果开启则必须要开启log-bin,开启二进制日志可以做级联复制。
sync-binlog = 0 | 1
是否立即同步二进制日志到硬盘,默认情况下,并不是每次写入时都将binlog与硬盘同步。因此如果操作系统或机器(不仅仅是MySQL服务器)崩溃,有可能binlog中最后的语句丢失了。要想防止这种情况,你可以使用sync_binlog全局变量(1是最安全的值,但也是最慢的)。
在MySQL 5.7.7之前,默认值为sync_binlog=0。MySQL 5.7.7及更高版本使用默认值为1,这是最安全的选择,但如上所述可能会影响性能。
server-id = 1
同一个复制拓扑中的每个服务器的id号必须唯一。
read-only = 0 | 1
锁定从服务器为只读,对于super用户不生效,如对只有select、update、insert、delete权限的用户生效。
skip-slave-start
告诉从服务器当服务器启动时不启动从服务器线程,使用START SLAVE语句在以后启动线程。
这四个参数是启用binlog/relaylog的校验,防止日志出错。
binlog-rows-query-log-events = 1
启用二进制日志记录事件相关的详细信息,可降低故障排除的复杂度。
relay_log_purge = 0 | 1
开启relay log自动purge操作,默认就是开启的。
relay_log_recovery = 0 | 1
当Slave发生crash导致重连master时,其不根据master-info.log的信息进行重连,而是根据relay-info中执行到master的位置信息重新开始拉master上的日志数据(不过需要确保日志依然存在于master上,否则就。。。)。
slave-paralles-workers = 0
默认是0,不开启从服务器的多线程复制,MySQL5.6中从服务器设置多线程复制只能针对多个库才有效,如果没有多个库开启多线程只会增加系统开销。(Mariadb中此参数为slave-paralles-threads)。
binlog-checksum = CRC32
二进制日志的校验算法。
master-verify-checksum = 1
启用此选项后,从库将检查从中继日志读取的校验和,如果发生不匹配,则从库将停止并出现错误。默认禁用。
slave-sql-verify-checksum = 1
启动此选项后,从库线程使用从中继日志读取的校验和来验证数据。在不匹配的情况下,从库停止并出现错误。设置此变量将立即对所有复制通道生效,包括运行通道。
report-port = 3306
report-host = master_ip/slave_ip
#提供复制报告的端口,和数据库端口一致。提供复制报告的主机,设置为当前主机的主机IP。当在从库设置了这两个参数时,在主库使用show slave hosts时才可以看到完整的从库信息。
master-info-repository = file | table
relay-log-info-repository = file | table
MySQL 5.6开始支持在SLAVE上把master信息和relay信息记录在事务表,用于解决从库宕机后的主从数据一致性问题。另外,如果你使用MySQL5.7的多源复制的话就必须的要求把master和relay信息存储到事务表中。具体当Slave发送Crash导致重连master时怎么会导致数据不一致性看这篇文章:http://www.ywnds.com/?p=7326。
sync_master_info = 10000
此变量对从库的影响取决于从库master_info_repository是否设置为FILE或TABLE,如以下段落所述。
master_info_repository = FILE。如果sync_master_info值大于0,则从库在每次事件之后将其master.info文件同步到磁盘(使用 fdatasync())。sync_master_info如果为0,则MySQL服务器不执行master.info文件到磁盘的同步,相反,服务器依赖于操作系统与其他文件一样定期刷新其内容到磁盘。
master_info_repository = TABLE。 如果sync_master_info值大于0,则在每个sync_master_info事件之后,从库更新其主信息存储到表。如果为0,则表不会更新。
默认值为sync_master_info=10000,设置此变量将立即对所有复制通道生效,包括正在运行的通道。
sync_relay_log_info = 10000
此变量对从库的影响取决于从服务器的relay_log_info_repository设置(FILE或 TABLE),如果是这样TABLE,还判断中继日志信息表使用的存储引擎是否是事务性的(如InnoDB)还是不非事务性的(如MyISAM)。这些因素使从库对sync_relay_log_info的值大于零的行为的影响如下表所示:
relay_log_info_repository = FILE。如果sync_relay_log_info值大于0,则从库在每次事件之后将其relay-log.info文件同步到磁盘(使用 fdatasync())。sync_relay_log_info如果为0,则MySQL服务器不执行relay-log.info文件到磁盘的同步,相反,服务器依赖于操作系统与其他文件一样定期刷新其内容到磁盘。
relay_log_info_repository = TABLE。 不管sync_relay_log_info值是大于0或等于0,则在每个sync_relay_log_info事件之后,从库都会更新信息存储到表,前提此表引擎是事务表。如果是非事务表(如Myisam),当sync_relay_log_info=0,则表不会更新。
默认值为sync_relay_log_info=10000,设置此变量将立即对所有复制通道生效,包括正在运行的通道。
sync_relay_log = 10000
如果此变量的值大于0,则在每个sync_relay_log事件写入中继日志后,MySQL服务器将其中继日志同步到磁盘(使用fdatasync() )。设置此变量将立即对所有复制通道生效,包括正在运行的通道。
设置sync_relay_log为0会导致磁盘无法同步,在这种情况下,服务器依赖于操作系统刷新中继日志的内容,与其他文件一样。
值为1是最安全的选择,因为在发生崩溃的情况下,从中继日志中最多丢失一个事件。但是,它也是最慢的选择(除非磁盘具有电池备份的缓存,这使得同步非常快)。
slave_transaction_retries = 10
如果从库SQL线程失败,是因为执行事务碰到InnoDB死锁或事务执行时间超过InnoDB的innodb_lock_wait_timeout,它会自动重试。默认值为10,设置此变量将立即对所有复制通道生效,包括运行通道。
从MySQL 5.7.5起,在从库上启用多线程时,支持重试事务。在以前的版本中,从库使用多线程时,slave_transaction_retries被视为等于0。
slave_skip_errors = off | ddl_exist_errors | all |
通常情况下,当从库发生错误时,复制停止,这样你就可以手动解决数据的不一致。此选项会导致从库SQL线程在slave_skip_errors返回选项值中列出的任何错误时继续复制,虽然不会发生错误了,但很大可能会导致主从数据不一致。所以非常不推荐使用all值来使从库忽略所有错误消息。
MySQL 5.7支持一个额外的速记值ddl_exist_errors,相当于错误代码列表1007,1008,1050,1051,1054,1060,1061,1068,1094,1146。附录B,错误,错误代码和常见问题列出了服务器错误代码。
gtid-mode = on
开启GTID复制功能(注意在Mariadb中此参数无效,因为GTID已经是标配了)。
enforce-gtid-consistency = true
启动强制GTID的一致性,如果开启GTID功能则此参数必须要开启(Maradb中此参数无效了)。slave在做同步复制时,无须找到binlog日志和POS点,直接change master to master_auto_position=1即可,自动根据GTID进行同步数据。
四、如何产生GTID?
GTID的生成受gtid_next控制。 在Master上,gtid_next是默认的AUTOMATIC,即在每次事务提交时自动生成新的GTID。它从当前已执行的GTID集合(即gtid_executed)中,找一个大于0的未使用的最小值作为下个事务GTID。同时在binlog的实际的更新事务事件前面插入一条set gtid_next事件。
以下是一条insert语句生成的binlog记录:
mysql> flush logs;
Query OK, 0 rows affected (0.01 sec)
mysql> insert into blog.info(id) values(1);
Query OK, 1 row affected (0.00 sec)
mysql> show master status;
+------------------+----------+--------------+------------------+------------------------------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+------------------------------------------+
| mysql-bin.000004 | 504 | | | 6edc34c8-d23d-11e6-b440-fa163e2a6390:1-7 |
+------------------+----------+--------------+------------------+------------------------------------------+
1 row in set (0.00 sec)
mysql> show binlog events in 'mysql-bin.000004';
+------------------+-----+----------------+-----------+-------------+-------------------------------------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+------------------+-----+----------------+-----------+-------------+-------------------------------------------------------------------+
| mysql-bin.000004 | 4 | Format_desc | 103306 | 123 | Server ver: 5.7.16-log, Binlog ver: 4 |
| mysql-bin.000004 | 123 | Previous_gtids | 103306 | 194 | 6edc34c8-d23d-11e6-b440-fa163e2a6390:1-6 |
| mysql-bin.000004 | 194 | Gtid | 103306 | 259 | SET @@SESSION.GTID_NEXT= '6edc34c8-d23d-11e6-b440-fa163e2a6390:7' |
| mysql-bin.000004 | 259 | Query | 103306 | 327 | BEGIN |
| mysql-bin.000004 | 327 | Rows_query | 103306 | 386 | # insert into blog.info(id) values(1) |
| mysql-bin.000004 | 386 | Table_map | 103306 | 433 | table_id: 219 (blog.info) |
| mysql-bin.000004 | 433 | Write_rows | 103306 | 473 | table_id: 219 flags: STMT_END_F |
| mysql-bin.000004 | 473 | Xid | 103306 | 504 | COMMIT /* xid=57 */ |
+------------------+-----+----------------+-----------+-------------+-------------------------------------------------------------------+
8 rows in set (0.00 sec)
在Slave上回放主库的binlog时,先执行set gtid_next …,然后再执行真正的insert语句,确保在主和备上这条insert对应于相同的GTID。
一般情况下,GTID集合是连续的,但使用多线程复制(MTS)以及通过gtid_next进行人工干预时会导致gtid空洞。
mysql> set gtid_next='6edc34c8-d23d-11e6-b440-fa163e2a6391:12';
Query OK, 0 rows affected (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> set gtid_next='AUTOMATIC';
Query OK, 0 rows affected (0.00 sec)
mysql> show master status;
+------------------+----------+--------------+------------------+-----------------------------------------------------------------------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-----------------------------------------------------------------------------------+
| mysql-bin.000004 | 706 | | | 6edc34c8-d23d-11e6-b440-fa163e2a6390:1-7,6edc34c8-d23d-11e6-b440-fa163e2a6391:12 |
+------------------+----------+--------------+------------------+-----------------------------------------------------------------------------------+
1 row in set (0.00 sec)
继续执行事务,MySQL会分配一个最小的未使用GTID,也就是从出现空洞的地方分配GTID,最终会把空洞填上。
mysql> show master status;
+------------------+----------+--------------+------------------+-----------------------------------------------------------------------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-----------------------------------------------------------------------------------+
| mysql-bin.000004 | 1016 | | | 6edc34c8-d23d-11e6-b440-fa163e2a6390:1-8,6edc34c8-d23d-11e6-b440-fa163e2a6391:12 |
+------------------+----------+--------------+------------------+-----------------------------------------------------------------------------------+
1 row in set (0.00 sec)
这意味着严格来说我们即不能假设GTID集合是连续的,也不能假定GTID序号大的事务在GTID序号小的事务之后执行,事务的顺序应由事务记录在binlog中的先后顺序决定。
五、如何修复GTID复制错误?
在基于GTID的复制拓扑中,要想修复Slave的SQL线程错误,过去的SQL_SLAVE_SKIP_COUNTER方式不再适用。需要通过设置gtid_next或gtid_purged完成,当然前提是已经确保主从数据一致,仅仅需要跳过复制错误让复制继续下去。
在从库上执行以下SQL:
mysql> stop slave;
Query OK, 0 rows affected (0.00 sec)
mysql> set gtid_next='e10c75be-5c1b-11e6-ab7c-000c296078ae:6';
Query OK, 0 rows affected (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> set gtid_next='AUTOMATIC';
Query OK, 0 rows affected (0.00 sec)
mysql> start slave;
Query OK, 0 rows affected (0.02 sec)
其中gtid_next就是跳过某个执行事务,设置gtid_next的方法一次只能跳过一个事务,要批量的跳过事务可以通过设置gtid_purged完成。假设下面的场景:
mysql> reset master;
Query OK, 0 rows affected (0.00 sec)
mysql> set global gtid_purged='6edc34c8-d23d-11e6-b440-fa163e2a6390:1-13,6edc34c8-d23d-11e6-b440-fa163e2a6391:12';
Query OK, 0 rows affected (0.01 sec)
mysql> show master status;
+------------------+----------+--------------+------------------+------------------------------------------------------------------------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+------------------------------------------------------------------------------------+
| mysql-bin.000001 | 154 | | | 6edc34c8-d23d-11e6-b440-fa163e2a6390:1-13,6edc34c8-d23d-11e6-b440-fa163e2a6391:12 |
+------------------+----------+--------------+------------------+------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
此时从库的Executed_Gtid_Set已经包含了主库上’1-13’和’12’的事务,再开启复制会从后面的事务开始执行,就不会出错了。注意,使用gtid_next和gtid_purged修复复制错误的前提是,跳过那些事务后仍可以确保主备数据一致。如果做不到,就要考虑pt-table-sync或者拉备份的方式了。