MySQL-GTID复制
1.GTID基本概念
传统的基于binlog position复制的方式有个严重的缺点:如果slave连接master时指定的binlog文件错误或者position错误,会造成遗漏或者重复,很多时候前后数据是有依赖性的,这样就会出错而导致数据不一致。
GTID的全称是global transaction id,mysql5.6开始支持,表示全局事务ID
分配方式:
uuid:trans_id
uuid
是每个mysql服务器都唯一的,记录在$datadir/auto.cnf
中。如果复制结构中,任意两台服务器uuid重复的话(比如直接冷备份时,auto.conf中的内容是一致的),在启动复制功能的时候会报错。这时可以删除auto.conf文件再重启mysqld。
(admin@g1-db-test:6005)[(none)]>select @@server_uuid; +--------------------------------------+ | @@server_uuid | +--------------------------------------+ | b3f6377f-52c5-11ea-92c4-be6f2b61965c | +--------------------------------------+ (admin@g1-db-test:6005)[(none)]>show variables like '%uuid%'; +---------------+--------------------------------------+ | Variable_name | Value | +---------------+--------------------------------------+ | server_uuid | b3f6377f-52c5-11ea-92c4-be6f2b61965c | +---------------+--------------------------------------+
trans_id
是事务ID,可以唯一标记某MySQL服务器上执行的某个事务。事务号从1开始,每提交一个事务,事务号加1。
例如"gtid_executed 5ad9cb8e-2092-11e7-ac95-000c29bf823d:1-6",表示该server_uuid上执行了从1到6的事务
2.GTID的生命周期
GTID如何持久化
gtid在master和slave上是一直持久化保存(即使删除了日志,也会记录到Previous_GTID中)的。它在master和slave上的生命周期如下:
1.master产生GTID
客户端发送DDL/DML给master上,master首先对此事务生成一个唯一的gtid,假如为uuid_xxx:1
,然后立即执行该事务中的操作。
注意,主从复制的情况下,sync-binlog基本上都会设置为1,这表示在每次提交事务时将缓存中的binlog刷盘。所以,在事务提交前,gtid以及事务相关操作的信息都在缓存中,提交后它们才写入到binlog file中,然后才会被dump线程dump出去。
换句话说,只有提交了的事务,gtid和对应的事务操作才会记录到binlog文件中。记录的格式是先记录gtid,紧跟着再记录事务相关的操作。
2.将binlog发送到slave
将binlog发送到slave所在服务器,并存储到relay log,slave读取GTID,并设置变量 gtid_next 的值为该gtid,表示下一个要操作的事务是该gtid。 gtid_next 是基于会话的,不同会话的gtid_next不同。
3.slave执行GTID
随后slave检测该gtid在自己的binlog中是否存在。如果存在,则放弃此gtid事务;如果不存在,则将此gtid写入到自己的binlog中,然后立刻执行该事务,并在自己的binlog中记录该事务相关的操作。
注意,slave上replay的时候,gtid不是提交后才写到自己的binlog file的,而是判断gtid不存在后立即写入binlog file。
通过这种在执行事务前先检查并写gtid到binlog的机制,不仅可以保证当前会话在此之前没有执行过该事务,还能保证没有其他会话读取了该gtid却没有提交。因为如果其他会话读取了该gtid会立即写入到binlog(不管是否已经开始执行事务),所以当前会话总能读取到binlog中的该gtid,于是当前会话就会放弃该事务。总之,一个gtid事务是决不允许多次执行、多个会话并行执行的。
4.slave不生成GTID
slave在重放relay log中的事务时,不会自己生成gtid,所以所有的slave(无论是何种方式的一主一从或一主多从复制架构)通过重放relay log中事务获取的gtid都来源于master,并永久保存在slave上。
3.GTID的好处
- 保证同一个事务在某slave上绝对只执行一次,没有执行过的gtid事务总是会被执行。
- 不用像传统复制那样保证binlog的坐标准确,因为根本不需要binlog以及坐标。
- 故障转移到新的master的时候很方便,简化了很多任务。
- 很容易判断master和slave的数据是否一致。只要master上提交的事务在slave上也提交了,那么一定是一致的。
当然,MySQL提供了选项可以控制跳过某些gtid事务,防止slave第一次启动复制时执行master上的所有事务而导致耗时过久。
虽然对于row-based和statement-based的格式都能进行gtid复制,但建议采用row-based格式。
4.GTID配置
master配置文件
[mysqld] datadir=/data socket=/data/mysql.sock log-bin=/data/master-bin # 必须项 sync-binlog=1 # 建议项 binlog_format=row # 建议项 server-id=100 # 必须项 log-error=/data/error.log pid-file=/data/mysqld.pid enforce_gtid_consistency=on # gtid复制需要加上的必须项 gtid_mode=on # gtid复制需要加上的必须项
slave配置文件
[mysqld] datadir=/data socket=/data/mysql.sock log-bin=/data/master-slave-bin # mysql 5.6必须项,mysql 5.7非必须项 sync-binlog=1 # 建议项 binlog_format=row # 建议项 relay-log=/data/slave-bin # 必须项 server-id=110 # 必须项 log-error=/data/error.log pid-file=/data/mysqld.pid enforce_gtid_consistency=on # 必须项 gtid_mode=on # 必须项
以上是mysql5.7配置,如果是mysql 5.6,那么在上面两个配置文件中需要加上log-slave-updates
选项
MySQL5.6的GTID复制模式,必须开启log_slave_updates参数,否则启动就报错,因为需要在binlog找到同步复制的信息(UUID:事务号),如果在密集型写的环境,比如双十一大促在线支付,这无疑增加了从库不必要的磁盘IO开销。
但在MySQL5.7里,官方终于做了调整,用一张gtid_executed系统表记录同步复制的信息(UUID:事务号),这样就可以不用开启log_slave_updates参数,减少了从库的压力
master上创建复制用户
grant replication slave on *.* to repl@'192.168.100.%' identified by 'P@ssword1!';
change master
change master to master_host='192.168.100.21', master_port=3306, master_auto_position=1; # gtid复制必须设置此项
5.GTID相关状态信息和变量
5.1show slave status 相关信息
Retrieved_Gtid_Set: b3f6377f-52c5-11ea-92c4-be6f2b61965c:1-4 Executed_Gtid_Set: 6a3aff53-6516-11e9-adbc-6c0b84be13e4:1-12, a54c7c36-52c6-11ea-9cf0-0a3805ad854b:1, b3f6377f-52c5-11ea-92c4-be6f2b61965c:1-4 Auto_Position: 1
Retrieved_Gtid_Set
:在开启了gtid复制(即gtid_mode=on)时,slave在启动io线程的时候会检查自己的relay log,并从中检索出gtid集合。也就是说,这代表的是slave已经从master中复制了哪些事务过来。检索出来的gtid不会再请求master发送过来。Executed_Gtid_Set
:在开启了gtid复制(即gtid_mode=on)时,它表示已经向自己的binlog中写入了哪些gtid集合。注意,这个值是根据一些状态信息计算出来的,并非binlog中能看到的那些。举个特殊一点的例子,可能slave的binlog还是空的,但这里已经显示一些已执行gtid集合了。Auto_Position
:开启gtid时是否自动获取binlog坐标。1表示开启,这是gtid复制的默认值。
5.2binlog中关于GTID的信息
5.3GTID重要变量
-
gtid_mode
:是否开启gtid复制模式。只允许on/off类的布尔值,不允许其他类型(如1/0)的布尔值,实际上这个变量是枚举类型的。要设置 gtid_mode=on ,必须同时设置 enforce_gtid_consistency 开。在MySQL 5.6中,还必须开启 log_slave_updates ,即使是master也要开启。因为5.6中gtid的持久化是通过binlog实现 -
enforce_gtid_consistency
:强制要求只允许复制事务安全的事务。 gtid_mode=on 时必须显式设置该项,如果不给定值,则默认为on。应该尽量将该选项放在 gtid_mode 的前面,减少启动mysqld时的检查。-
不能在事务内部创建和删除临时表。只能在事务外部进行,且autocommit需要设置为1。
-
不能执行 create table ... select 语句。该语句除了创建一张新表并填充一些数据,其他什么事也没干。
-
不能在事务内既更新事务表又更新非事务表。
-
gtid_executed
:已经执行过的GTID。 reset master 会清空该项的全局变量值。gtid_purged
:已经purge掉的gtid。要设置该项,必须先保证 gtid_executed 已经为空,这意味着也一定会同时设置该项为空。在slave上设置该项时,表示稍后启动io线程和SQL线程都跳过这些gtid,slave上设置时应该让此项的gtid集合等于master上 gtid_executed 的值。gtid_next
:表示下一个要执行的gtid事务。- gtid_owned:这既是一个Global级别的变量,又是一个Session级别的变量,是只读变量。Global级别的gtid_owned表示当前实例正在执行中的GTID,以及对应的线程id。Session级别的gtid_owned一般情况下是空的。
需要注意,master和slave上都有gtid_executed
和gtid_purged
,它们代表的意义有时候是不同的。
- binlog_gtid_simple_recovery :binlog_gtid_simple_recovery 在没有开启这个参数的时候,MySQL重启或者恢复都会扫描全部的binlog获取gtid_executed信息,这样做耗时会很长。开启这个参数之后MySQL只会扫描第一个binlog和最后一个binlog文件。还有一个需要注意的是在中途开启GTID的时候,也会扫描前面所有的binlog直到获取得到
官方文档解释:
此变量控制MySQL启动或重新启动时在搜索GTID期间如何迭代二进制日志文件。
在MySQL版本5.7.5中,此变量被添加为simple_binlog_gtid_recovery,在MySQL版本5.7.6中将其重命名为binlog_gtid_simple_recovery。
当binlog_gtid_simple_recovery = FALSE时,迭代二进制日志文件的方法是:
要初始化gtid_exected,请从最新文件中迭代二进制日志文件,并在具有任何Previous_gtids_log_event的第一个二进制日志处停止。
从此二进制日志文件中读取Previous_gtids_log_event和Gtid_log_events中的所有GTID。
该GTID集在内部存储,称为gtids_in_binlog。
gtid_exected的值计算为该集合与存储在mysql.gtid_executed表中的GTID的并集。
如果您有大量没有GTID事件的二进制日志文件(例如,在gtid_mode = OFF时创建),则此过程可能需要很长时间。
要初始化gtid_purged,将二进制日志文件从最旧的迭代到最新的,
在第一个二进制日志中停止,该日志包含一个非空(具有至少一个GTID)的Previous_gtids_log_event
或至少具有一个Gtid_log_event。从此二进制日志中,它读取Previous_gtids_log_event。
从gtids_in_binlog中减去此GTID集,并将结果存储在内部变量gtids_in_binlog_not_purged中。
gtid_purged的值初始化为gtid_executed的值减去gtids_in_binlog_not_purged。
当binlog_gtid_simple_recovery = TRUE(这是MySQL 5.7.7及更高版本中的默认设置)时,服务器仅迭代最旧和最新的二进制日志文件,并且gtid_purged和gtid_executed的值仅基于在这些文件中找到的Previous_gtids_log_event或Gtid_log_event来计算。这样可确保在服务器重新启动期间或清除二进制日志时仅迭代两个二进制日志文件。
https://blog.csdn.net/n88Lpo/article/details/79546948
https://yq.aliyun.com/articles/589903
5.7.6以下中默认
-
simplified_binlog_gtid_recovery=flase
5.7.6以上中默认
-
binlog_gtid_simple_recovery=true
6.GTID持久化
6.1binlog持久化 5.7.5以前
Previous gtid Event是包含在每一个binlog的开头用于描述所有以前binlog所包含的全部Gtid的一个集合(包括已经删除的binlog)。
在5.6中如果不开启Gtid,那么binlog是不会包含这个Previous gtid Event的,但是在5.7中不开启Gtid也会包含这个Previous gtid Event,实际这一点的改变其意义也是非常巨大,简单的说他为快速扫描binlog(binlog_gtid_simple_recovery=ture)获得正确Gtid集合提供了基础,否则将会扫描大量的binlog,从而浪费I/O性能,这是5.6中一个非常严重的问题。还有一个需要注意的问题就是,在没有开启GTID的时候,Previous gtid Event的值显示的empty,这个时候中途开启GTID的话,MySQL重启读取binlog获取gtid_executed信息的话还是会重新读取旧的binlog直到Previous gtid Event和获取GTID event.
6.2mysql.gtid_executed表5.7.5以后
mysql5.7.5版本以后mysql新增了gtid_executed表,每一行表示一个gtid或者gtid集合,包括source_uuid,集合开始和结束的事务id.
(admin@g1-db-test:6005)[mysql]>select * from mysql.gtid_executed; +--------------------------------------+----------------+--------------+ | source_uuid | interval_start | interval_end | +--------------------------------------+----------------+--------------+ | 6a3aff53-6516-11e9-adbc-6c0b84be13e4 | 1 | 1 | | 6a3aff53-6516-11e9-adbc-6c0b84be13e4 | 2 | 7 | | 6a3aff53-6516-11e9-adbc-6c0b84be13e4 | 8 | 12 | +--------------------------------------+----------------+--------------+
只有gtid_mode为on或者on_permissive时,gtid才会保存到gtid_executed表中.gtid存储在该表时,不会考虑是否开启的binlog;
当没开启binlog:每个事务都会记录到gtid_executed中
开启了binlog:每个事务不仅会记录到gtid_executed表中,而且当binlog rotate或者服务器关闭时,服务器会将gtid信息写入新的binlog.如果服务器异常关闭,gtid不会被存入gtid_executed表中.这种情况下,mysql在恢复时,会讲这些gtid信息添加到表中并写入gtid_executed系统变量中.
gtid_executed表压缩
随着数据库不断更新,gtid_executed表中gtid信息会越来越多,后期通过事务的间隔来代替原来的每个gtid信息.减少磁盘空间消耗
mysql启用gtid时,服务器会定期对gtid_executed进行压缩:
控制参数
gtid_executed_compression_period | 1000 默认值1000,表示每执行1000个事务进行一次压缩,设置为0表示不压缩
注意:开启binlog,且gtid_executed_compression_period 没有使用时,mysql binlog轮换会引起gtid_executed表自动压缩
mysql中有一个单独的后台线程来执行表压缩
(admin@g1-db-test:6005)[mysql]>select * from performance_schema.threads where name like '%gtid%'\G *************************** 1. row *************************** THREAD_ID: 43 NAME: thread/sql/compress_gtid_table TYPE: FOREGROUND PROCESSLIST_ID: 1 PROCESSLIST_USER: NULL PROCESSLIST_HOST: NULL PROCESSLIST_DB: NULL PROCESSLIST_COMMAND: Daemon PROCESSLIST_TIME: 337418 PROCESSLIST_STATE: Suspending PROCESSLIST_INFO: NULL PARENT_THREAD_ID: 1 ROLE: NULL INSTRUMENTED: YES HISTORY: YES CONNECTION_TYPE: NULL THREAD_OS_ID: 26308
该线程睡眠知道执行gtid_executed_compression_period个事务后,唤醒该线程执行压缩操作,然后继续睡眠,如此循环,禁用binlog并将此参数设置为0的话,该线程永远不会被唤醒
7.GTID和binlog的关系
GTID event 结构
Previous_gtid_log_event
Previous_gtid_log_event 在每个binlog 头部都会有每次binlog rotate的时候存储在binlog头部Previous-GTIDs在binlog中只会存储在这台机器上执行过的所有binlog,不包括手动设置gtid_purged值。换句话说,如果你手动set global gtid_purged=xx; 那么xx是不会记录在Previous_gtid_log_event中的。
GTID和Binlog之间的关系是怎么对应的呢? 如何才能找到GTID=? 对应的binlog文件呢?
假设有4个binlog: bin.001,bin.002,bin.003,bin.004
bin.001 : Previous-GTIDs=empty; binlog_event有: 1-40
bin.002 : Previous-GTIDs=1-40; binlog_event有: 41-80
bin.003 : Previous-GTIDs=1-80; binlog_event有: 81-120
bin.004 : Previous-GTIDs=1-120; binlog_event有: 121-160
假设现在我们要找GTID=$A,那么MySQL的扫描顺序为:
- 从最后一个binlog开始扫描(即: bin.004)
- bin.004的Previous-GTIDs=1-120,如果$A=140 > Previous-GTIDs,那么肯定在bin.004中
- bin.004的Previous-GTIDs=1-120,如果$A=88 包含在Previous-GTIDs中,那么继续对比上一个binlog文件 bin.003,然后再循环前面2个步骤,直到找到为止.
====== 新的复制协议 COM_BINLOG_DUMP_GTID ======
- Slave sends to master range of identifiers of executed transactions to master
- Master send all other transactions to slave
- 同样的GTID不能被执行两次,如果有同样的GTID,会自动被skip掉。
slave1 : 将自身的UUID1:1 发送给 master,然后接收到了 UUID1:2,UUID1:3 event
slave2 : 将自身的UUID1:1,UUID1:2 发送给 master,然后接收到了UUID1:3 event
Binlog dump
最开始的时候,MySQL只支持一种binlog dump方式,也就是指定binlog filename + position,向master发送COM_BINLOG_DUMP命令。在发送dump命令的时候,我们可以指定flag为BINLOG_DUMP_NON_BLOCK,这样master在没有可发送的binlog event之后,就会返回一个EOF package。不过通常对于slave来说,一直把连接挂着可能更好,这样能更及时收到新产生的binlog event。在MySQL 5.6之后,支持了另一种dump方式,也就是GTID dump,通过发送COM_BINLOG_DUMP_GTID命令实现,需要带上的是相应的GTID信息.
8.GTID的优缺点
GTID的优点
- 一个事务对应一个唯一ID,一个GTID在一个服务器上只会执行一次;
- GTID是用来代替传统复制的方法,GTID复制与普通复制模式的最大不同就是不需要指定二进制文件名和位置;
- 减少手工干预和降低服务故障时间,当主机挂了之后通过软件从众多的备机中提升一台备机为主机;
GTID复制是怎么实现自动同步,自动对应位置的呢?
比如这样一个主从架构:ServerC <-----ServerA ----> ServerB
即一个主数据库ServerA,两个从数据库ServerB和ServerC
当主机ServerA 挂了之后 ,此时ServerB执行完了所有从ServerA 传过来的事务,ServerC 延时一点。这个时候需要把 ServerB 提升为主机 ,Server C 继续为备机;当ServerC 链接ServerB 之后,首先在自己的二进制文件中找到从ServerA 传过来的最新的GTID,然后将这个GTID 发送到ServerB ,ServerB 获得这个GTID之后,就开始从这个GTID的下一个GTID开始发送事务给ServerC。这种自我寻找复制位置的模式减少事务丢失的可能性以及故障恢复的时间。
GTID的缺点(限制)
- 不支持非事务引擎;
- 不支持create table ... select 语句复制(主库直接报错);(原理: 会生成两个sql, 一个是DDL创建表SQL, 一个是insert into 插入数据的sql; 由于DDL会导致自动提交, 所以这个sql至少需要两个GTID, 但是GTID模式下, 只能给这个sql生成一个GTID)
- 不允许一个SQL同时更新一个事务引擎表和非事务引擎表;
- 在一个复制组中,必须要求统一开启GTID或者是关闭GTID;
- 开启GTID需要重启 (mysql5.7除外);
- 开启GTID后,就不再使用原来的传统复制方式;
- 对于create temporary table 和 drop temporary table语句不支持;
- 不支持sql_slave_skip_counter;
9.GTID和传统复制的切换
9.1 传统复制升级到GTID复制
1.前提
1.要求所有的mysql版本5.7.6或更高的版本。
2.目前拓扑结构中所有的mysql的gtid_mode的值为off状态。
3.如下的操作步骤都是有序的,不要跳跃着进行。
2.GTID_MODE变量值说明:
- OFF 新事务是非GTID, Slave只接受不带GTID的事务,传送来GTID的事务会报错
- OFF_PERMISSIVE 新事务是非GTID, Slave即接受不带GTID的事务也接受带GTID的事务
- ON_PERMISSIVE 新事务是GTID, Slave即接受不带GTID的事务也接受带GTID的事务
- ON 新事务是GTID, Slave只接受带GTID的事务
3.gtid_mode设置顺序
off<--->OFF_PERMISSIVE<--->ON_PERMISSIVE<--->ON
不能跳跃执行,会报错
4.传统复制模式升级为GTID复制模式
前提:都开启了binlog,GTID_MODE=OFF
1.每台服务器都设置enforce_gtid_consistency=warn
set @@global.enforce_gtid_consistency=warn;
注意:执行完这条语句后,如果出现GTID不兼容的语句用法,在错误日志会记录相关信息,那么需要调整应该程序避免不兼容的写法,直到完全没有产生不兼容的语句,可以通过应该程序去排查所有的sql,也可以设置后观察错误日志一段时间,这一步非常重要.
2.每台服务器都设置enforce_gtid_consistency=on
set @@global.enforce_gtid_consistency=on;
确保左右事务不能违反GTID一致性
3.每台服务器设置set global gtid_mode=OFF_PERMISSIVE;
set global gtid_mode=OFF_PERMISSIVE;
表示新事务是匿名的,同时允许复制的事务是GTID或者匿名的(没有设置gtid_next的事务就是匿名的)
4.每台服务器设置set global gtid_mode=ON_PERMISSIVE;
set global gtid_mode=ON_PERMISSIVE;
表示新的事务使用GTID.同时允许复制的事务为GTID或者匿名事务
5.等待Ongoing_anonymous_transaction_count变为0
(admin@g1-db-test:6005)[(none)]>show global status like '%ongoing%'; +-------------------------------------+-------+ | Variable_name | Value | +-------------------------------------+-------+ | Ongoing_anonymous_transaction_count | 0 | +-------------------------------------+-------+
表示以标记为匿名的正在进行的事务数量,0表示没有正在进行的匿名事务
6.在每台服务器上执行set global gtid_mode=ON
set global gtid_mode=on;
7.修改配置文件加入
gtid-mode=ON enforce_gtid_consistency=on
8.change master to master_auto_position=1;
-----------------------------------------------------------------
参考文档:
https://www.cnblogs.com/f-ck-need-u/p/9164823.html 大神博客
http://mysql.taobao.org/monthly/2015/01/03/ 内核月报
https://www.jianshu.com/p/87f66cdeb49c 重庆八怪的GTID系列
https://blog.51cto.com/11819159/2083512GTID持久化
https://www.cnblogs.com/zejin2008/p/7705473.html mysql5.6GTID原理
https://www.cnblogs.com/zejin2008/p/7705473.html5.6,5,7的区别
https://segmentfault.com/a/1190000014455382传统复制-》GTID模式复制