MySQL5.7 GTID 浅析
https://yq.aliyun.com/articles/68441
摘要: # GTID 简介 GTID (global transaction identifier)在MySQL5.6时引入,GTID是事务的全局唯一标识。GTID结构如下 ``` GTID = source_id:transaction_id ``` source_id:执行事务的原始实例的sever_uuid, 此事务GTID在备库apply时也不变。 transaction_id:事务的
GTID 简介
GTID (global transaction identifier)在MySQL5.6时引入,GTID是事务的全局唯一标识。GTID结构如下
GTID = source_id:transaction_id
source_id:执行事务的原始实例的sever_uuid, 此事务GTID在备库apply时也不变。
transaction_id:事务的执行编号,binlog_order_commits=1时,此编号根据事务的提交顺序严格递增。GTID是在binlog flush时生成的,因此同一个serverid的GTID在binglog中是严格有序的。binlog_order_commits=0时,GTID在binlog中也是序的,但并不一定与提交的顺序一致。
binlog_order_commits=0会影响XtraBackup工具的备份,但不会影响innobackup工具的备份
XtraBackup会从innodb事务page获取最后提交事务的binlog位点信息,binlog_order_commits=0时事务提交顺序和binlog顺序不一定一致,这样此位点前可能存在部分prepare状态的事务,这些事务在备份恢复后会丢失。
而innobackup的位点信息是在加备份锁前提下从show master status/show slave status中获取的,位点前的事务都是已提交的。
支持GTID后,备库启动时不再需要通过位点信息从主库来拉取binlog,而是根据备库本身已执行和拉取的gtid去主库查找第一个未执行的GTID,从此GTID位置开始拉取binlog。
新增了COM_BINLOG_DUMP_GTID命令
备库
备库封装COM_BINLOG_DUMP_GTID命令,包含备库的gtid_executed(已执行的GTID和当前已拉取的GTID的并集)request_dump():
gtid_executed.add_gtid_set(mi->rli->get_gtid_set())
gtid_executed.add_gtid_set(gtid_state->get_executed_gtids())
主库
主库接收COM_BINLOG_DUMP_GTID命令,从最新的binlog开始反向遍历查找的binlog, 依次读取PREVIOUS_GTIDS_LOG_EVENT, 直到PREVIOUS_GTIDS_LOG_EVENT记录的gtid_set是备库发过来的gtid子集为止。com_binlog_dump_gtid():
Binlog_sender::init
Binlog_sender::check_start_file(
mysql_bin_log.find_first_log_not_in_gtid_set
Binlog_sender::run
mysql5.7 gtid相对5.6主要有以下变化
- gtid_mode可以动态设置,支持gtid模式和非gtid模式之间的复制
- 增加了gtid_executed表
gtid_mode
MySQL5.7(>= 5.7.6) gtid_mode支持动态修改,gtid_mode取值可选择如下
OFF: Both new and replicated transactions must be anonymous.
OFF_PERMISSIVE: New transactions are anonymous. Replicated transactions can be either anonymous or GTID transactions.
ON_PERMISSIVE: New transactions are GTID transactions. Replicated transactions can be either anonymous or GTID transactions.
ON: Both new and replicated transactions must be GTID transactions.
OFF_PERMISSIVE时支持GTID模式的实例向非GTID模式的实例的复制。
ON_PERMISSIVE:时支持非GTID模式的实例向GTID模式的实例的复制。此模式下,可支持低版本实例(<=5.5)向5.7高版本GTID实例的复制,从而为低版本实例(不支持GTID)平滑升级为5.7GTID实例提供了便利。
需要吐槽的是MySQL5.6目前还不支持低版本实例(<=5.5)向5.6高版本GTID实例的复制, 需要修改代码打开此限制才可以。
另外,gtid_mode动态修改不支持跳跃修改。例如,如果当前值为OFF_PERMISSIV,只支持修改为OFF或ON_PERMISSIVE,不支持修改为ON。
MySQL 5.7 gtid_mode=on时需要设置enforce_gtid_consistency=1. MySQL5.6还需要另外设置 --log-bin, --log-slave-updates,而5.7是不需要的,这得益于5.7的gtid_executed表。
gtid_executed表
gtid_executed表存储的是已执行的GTID集合信息,此信息不一定是实时的。gtid_executed表结构如下
CREATE TABLE gtid_executed (
source_uuid CHAR(36) NOT NULL,
interval_start BIGINT(20) NOT NULL,
interval_end BIGINT(20) NOT NULL,
PRIMARY KEY (source_uuid, interval_start)
)
gtid_executed表的益处
有了gtid_executed表后,GTID模式下允许关闭binlog,允许设置log-slave-updates=0。这样带来的以下好处
- 开启GTID模式下,可以关闭备库的binlog或设置log-slave-updates=0,GTID信息仍然会保存在gtid_executed表中。这样备库依然可以正常复制,同时省去了记录binlog的开销。
AliSQL 5.6在这块也做了优化,备库SQL线程的产生的binlog只记录GTID EVENT信息,不记录实际操作的event, 因此减少了binglog的量, 并且能够保证正常的复制。
- 开启GTID模式下,由于gtid_executed表是持久化的,即使人为删除了备库的binlog,复制依然可以通过gtid_executed表恢复。
gtid_executed表的更新
gtid_executed在binlog开启和关闭的情况下都会更新
binlog开启
每次rotate或shutdown时存储PREVIOUS_GTIDS_LOG_EVENT,只记录最后一个binlog的gtid信息。参考save_gtids_of_last_binlog_into_tablebinlog关闭或log_slave_updates=0
每次事务提交时都存储GTID
MYSQL_BIN_LOG::gtid_end_transaction():
if (!opt_bin_log || (thd->slave_thread && !opt_log_slave_updates))
gtid_state->save(thd)
ha_commit_trans():
if (!opt_bin_log || (thd->slave_thread && !opt_log_slave_updates))
gtid_state->save(thd)
reset master
reset master 会重置表,以delete方式删除所有数据(非truncate)
Gtid_table_persistor::reset
delete_all(table)
gtid_executed表的compress
更新gtid_executed表信息时,每次都是insert一条数据,而不是update方式,update容易产生行冲突,insert可以提高并发。而insert的副作用是导致gtid_executed表行记录数不断增加。因此,专门提供了一个compress线程用来压缩gtid_executed表。
以源码中的注释来说明compress过程,具体可参考Gtid_table_persistor::compress_in_single_transaction
Read each row by the PK(sid, gno_start) in increasing order,
compress the first consecutive range of gtids.
For example,
1 1
2 2
3 3
6 6
7 7
8 8
After the compression, the gtids in the table is compressed as following:
1 3
6 6
7 7
8 8
全表扫描,依次找到连续一行删一行,删除(2,2),(3,3),最后更新第一行的结束值(1,1)更新为(1,3)
这里有个有趣的bug, 设置super_read_only导致compress事务在提交时检查read_only失败,然后回滚事务。随着gtid_executed表数据的增加,compress线程的事务越来越大,更新失败然后回滚的代价越来越大。
compress线程是被动触发的
mysql_cond_wait(&COND_compress_gtid_table, &LOCK_compress_gtid_table);
以下两种情况会唤醒compress线程
mysql_cond_signal(&COND_compress_gtid_table);
- 插入单个GTID时通过参数gtid_executed_compression_period来控制唤醒compress,此种情况发生在binlog关闭或log_slave_updates=0事务提交时。
- 插入GTID集合每次都会唤醒compress,这种情况发生在binlog开启时, binlog rotate或实例关闭时。
启动时gtid_executed表的处理
实例启动时,会读取gtid_execute表信息来构建以下信息
executed_gtids:已执行的gtid信息,是gtid_executed表和binlog中gtid的并集。即gtid_executed。
lost_gtids:已经purged的gtid。即gtid_purged。
构建executed_gtids
1 读gtid_executed表的GTID信息赋值给exeucted_gtids, 参考read_gtid_executed_from_table
2 将binlog中比gtid_executed表中多的GTID补进来gitds_in_binlog_not_in_table.add_gtid_set(>ids_in_binlog);
gtids_in_binlog_not_in_table.remove_gtid_set(executed_gtids);
gtid_state->save(>ids_in_binlog_not_in_table) //将binlog比表中多的补进来
executed_gtids->add_gtid_set(>ids_in_binlog_not_in_table);
gtids_in_binlog是逆向查找binlog,直到找到第一个包含PREVIOUS_GTIDS_LOG_EVENT的binlog为止, 读取此binlog文件的PREVIOUS_GTIDS_LOG_EVENT和GTID_LOG_EVENT构成gtids_in_binlog
构建 lost_gtids
lost_gtids = executed_gtids -
(gtids_in_binlog - purged_gtids_from_binlog)
= gtids_only_in_table + purged_gtids_from_binlog;
purged_gtids_from_binlog是正向查找binlog,可以从第一个包含GTID_LOG_EVENT的binlog的PREVIOUS_GTIDS_LOG_EVENT中获取。
有一种情况比较特殊,5.6 升级5.7时,有一种情况会导致binlog中有PREVIOUS_GTIDS_LOG_EVENT但没有GTID_LOG_EVENT。如下面的注释所示,真正的purged_gtids_from_binlog应该从master-bin.N+2的PREVIOUS_GTIDS_LOG_EVENT中获取
/*
This branch is only reacheable by a binary log. The relay log
don't need to get lost_gtids information. A 5.6 server sets GTID_PURGED by rotating the binary log. A 5.6 server that had recently enabled GTIDs and set GTID_PURGED
would have a sequence of binary logs like: master-bin.N : No PREVIOUS_GTIDS (GTID wasn't enabled)
master-bin.N+1: Has an empty PREVIOUS_GTIDS and a ROTATE
(GTID was enabled on startup)
master-bin.N+2: Has a PREVIOUS_GTIDS with the content set by a
SET @@GLOBAL.GTID_PURGED + has GTIDs of some
transactions. If this 5.6 server be upgraded to 5.7 keeping its binary log files,
this routine will have to find the first binary log that contains a
PREVIOUS_GTIDS + a GTID event to ensure that the content of the
GTID_PURGED will be correctly set (assuming binlog_gtid_simple_recovery
is not enabled).
*/
原因在于MySQL5.6在set gtid_purged时是通过切换文件(rotate_and_purge )将gtid_purged存储在PREVIOUS_GTIDS_LOG_EVENT中.
而MySQL5.7在set gtid_purged时并不切换文件,gtid_purged直接存储到gtid_executed表中。
参数 binlog_gtid_simple_recovery
官网对binlog_gtid_simple_recovery=false进行了详细的解释。主要说明了binlog_gtid_simple_recovery=false时正向查找binlog获取gtid_purged(对应上节的lost_gtids)和逆向查找binlog获取gtid_executed(对应上节的executed_gtids)可能需要遍历较多的binlog文件,上节也介绍了遍历查找的方法。
但官网只是简单的介绍了binlog_gtid_simple_recovery=true时只需要查找最新或最老的binlog文件即可,至于为什么可以这样做没有明确说明。
以下是我的个人理解,
对于gtid_executed只需要读最新的binlog文件,即使最新的binlog文件没有PREVIOUS_GTIDS_LOG_EVENT也没有关系,因为最老的PREVIOUS_GTIDS_LOG_EVENT在binlog roate时已经写入gtid_executed表,根据上节的gtid_executed获取逻辑会读取gtid_executed表,最后获取的gtid_executed是完整的。
对于gtid_purged只需要读取老的binlog文件, 如果最老的binlog文件没有PREVIOUS_GTIDS_LOG_EVENT,同时最新的binlog文件也没有PREVIOUS_GTIDS_LOG_EVENT的情况下,根据上节的lost_gtids恢复逻辑
lost_gtids = executed_gtids -
(gtids_in_binlog - purged_gtids_from_binlog)
gtids_in_binlog和purged_gtids_from_binlog都为空,最后lost_gtids=executed_gtids,这显然是不正确的。这里我认为lost_gtids并不是一个重要的值,只在set gtid_purge时会修改,即使不正确也不影响正常复制。
GTID三个限制
enforce-gtid-consistency=ON时,以下三类语句时不支持的
CREATE TABLE ... SELECT statements
CREATE TEMPORARY TABLE or DROP TEMPORARY TABLE statements inside transactions
Transactions or statements that update both transactional and nontransactional tables. There is an exception that nontransactional DML is allowed in the same transaction or in the same statement as transactional DML, if all nontransactional tables are temporary.
而实际上这个限制没有必要这么严格,
CREATE TABLE ... SELECT statements
对于binlog_format=row, gtid_next='automatic'时可以放开限制。
生成的binlog包含两个GTID, 一个是建表语句,一个是包含多个insert的事务。事务中包含事务表和非事务表
对于gtid_next='automatic'时可以放开限制。
生成的binlog包含两个GTID, 一个是所有非事务表的,一个是所有事务表的。
对update多表(包含事务表和非事务表)此时需额外要求binlog_format=row。
总结
MySQL 5.7 在GTID上有了较大改进,但GTID的三个使用限制仍然存在,期待后期有所改进。
MySQL5.7 GTID 浅析的更多相关文章
- MySQL5.6 GTID新特性实践
MySQL5.6 GTID新特性实践 GTID简介 搭建 实验一:如果slave所需要事务对应的GTID在master上已经被purge了 实验二:忽略purged的部分,强行同步 本文将简单介绍基于 ...
- MySQL5.7 GTID在线开启与关闭【转】
当前场景 当前某些业务还有未开启GTID服务组,升级5.7后,如何检测是否符合开启GTID条件,如何在线修改切换使用GTID:已经升级5.7后,已经开启GTID,如何快速回滚后退: 线上gtid如 ...
- MySQL5.6 GTID、多线程复制
MySQL5.6新特性GTID.多线程复制 在Oracle发布MySQL5.6看到众多新特性之后很兴奋,包括对复制的改进.在MySQL5.5半同步复制之后MySQL5.6又引入GTID.多线程复制,在 ...
- 阿里云ECS服务器上搭建keepalived+mha+mysql5.6+gtid+一主两从+脚本判断架构踩的坑
最近,公司项目搭建了一套后端数据库架构,不是在RDS,是在阿里云的ECS服务器上搭建keepalived.mha.mysql5.6.gtid.一主两从架构,目前还没有实现读写分离,以后架构升级,可能代 ...
- MySQL5.7 GTID学习笔记
GTID(global transaction identifier)是对于一个已提交事务的全局唯一编号,前一部分是server_uuid,后面一部分是执行事务的唯一标志,通常是自增的. 下表整理了G ...
- MySQL5.7 GTID学习笔记,[MySQL 5.6] GTID实现、运维变化及存在的bug
GTID(global transaction identifier)是对于一个已提交事务的全局唯一编号,前一部分是server_uuid,后面一部分是执行事务的唯一标志,通常是自增的. 下表整理 ...
- MySQL5.6 GTID Replication
MySQL 5.6 的新特性之一,是加入了全局事务 ID (Global Transaction ID) 来强化数据库的主备一致性,故障恢复,以及容错能力.官方文档:http://dev.mysql. ...
- MySQL5.6 GTID方式,配置主从
迁移数据到从库 数据导出: mysqldump -uroot -p111111 -h127. -P3306 -q --single-transaction -R -E --triggers --def ...
- Mysql基于GTID复制模式-运维小结 (完整篇)
先来看mysql5.6主从同步操作时遇到的一个报错:mysql> change master to master_host='192.168.10.59',master_user='repli' ...
随机推荐
- Fatal error: Call to undefined function mb_strlen()
php配置的时候出现:Fatal error: Call to undefined function mb_strlen() 表示php不能加载mbstring模块,在php 的配置文件php.in ...
- USB抓包工具Bus Hound
/********************************************************************** * USB抓包工具Bus Hound * 说明: * 之 ...
- Linux /etc/password 文件详解
root2:x:0:0::/home/root2:/bin/bash[用户名]:[密码]:[UID]:[GID]:[身份描述]:[主目录]:[登录shell] 其中: ⒈[用户名]是passwd文件里 ...
- 20155208徐子涵 2016-2017-2 《Java程序设计》第6周学习总结
20155208徐子涵 2016-2017-2 <Java程序设计>第6周学习总结 教材学习内容总结 10.1.1 1.Java将输入/输出抽象化为串流,数据有来源及目的地,衔接两者的是串 ...
- java-抽象类的特点
1.抽象类和抽象方法必须用abstract关键字修饰. - abstract class 类名() - public abstract void eat(); 2.抽象类不一定有抽象方法,有抽象方法的 ...
- Washing Text Animation
https://www.youtube.com/watch?v=q0_koJLc0OgBlender Tutorial: Washing Text Animation 需要用到插件, 进入用户设置的插 ...
- LeetCode - Number of Distinct Islands
Given a non-empty 2D array grid of 0's and 1's, an island is a group of 1's (representing land) conn ...
- LeetCode - Flood Fill
An image is represented by a 2-D array of integers, each integer representing the pixel value of the ...
- 用Git向gitHub上传项目
用Git向gitHub上传项目 1.安装git 2.在git安装目录下,运行git-bash.exe 如图所示 3.在git中绑定你注册gitHub是的用户名.邮箱. $ git config -- ...
- 使用nvm-windows安装nodejs遇到的问题(转载)
##使用nvm-windows安装nodejs遇到的问题 ####问题概述 由于国内网络限制导致使用nvm(nvm-windows,以下都使用nvm简称)安装nodejs超时或出现远程连接被关闭的问题 ...