MySQL GTID详解

GTID介绍
从MySQL 5.6.5开始支持GTID,每一个在主库上提交的事务在复制集群中可以生成一个唯一的ID。
一个GTID在一个服务器上只执行一次,避免重复执行导致主从不一致或者数据混乱。
在MySQL 5.6.x中,如果使用GTID复制,则从库必须使用系统变量 log_bin 来启用二进制日志记录功能,
系统变量 log_slave_updates 将主库的二进制日志记录到从库自身的二进制日志中。

在MySQL 5.7.x中新增了mysql.gtid_excuted表来持久化GTID信息,所以可以不启用系统变量 log_bin 和 log_slave_updates

当主库事务被提交并将二进制日志写入二进制日志文件中,会为其分配新的GTID,保证事务的GTID单调递增且生成的数字之间没有间隔。
如果事务未写入二进制日志文件(例如事务被过滤,或者事务是只读的),则在源Server上不会为其分配GTID。

在从库上应用主库的二进制日志时会保留主库事务的GTID,即使从库在复制事务时进行了过滤,主库事务的GTID也会在从库中持久化,
即数据可能已被过滤,但是GTID仍然会被记录下来。mysql.gtid_excuted表用于保存从库中应用的所有事务已分配的GTID。
在MySQL 5.7.x中当启用系统变量 log_bin 和 log_slave_updates 时,mysql.gtid_excuted表中的GTID SET不包括最后一个正在使用的二进制日志文件中的GTID。

从库在应用主库的二进制日志时,碰到相同GTID的事务时会跳过,主库的事务在从库的应用即不会重复执行,这可以保证主从库数据的一致性。

mysql.gtid_excuted表仅供MySQL Server内部使用,当从库禁用二进制日志功能或关闭系统变量log_slave_updates时,
每个事务的GTID都会被实时记录到该表。当执行FLUSH LOG语句时,二进制日志会执行切换,上一个二进制日志文件中
的GTID将会被记录导该表中。当二进制日志被丢失,该表中的GTID记录不会丢失。但当执行RESET MASTER该表中的内容会被清空。

仅当gtid_mode被设置为ON或ON_PERMISSIVE时,GTID才存储在mysql.gtid_excuted表中。
是否在该表中实时存储GTID取决于变量log_bin和log_slave_updates的值,如下:
(1)如果禁用二进制日志(必须是注释掉变量log_bin,而不是将其设置为OFF),或者禁用log_slave_updates(设置为OFF或0),
则MySQL Server在每个事务提交时将事务的GTID一并记录到该表。

(2)如果启用了变量log_bin和log_slave_updates,则只在切换二进制日志或者关闭MySQL Server时,
将先前二进制日志的所有事务的GTID写入mysql.gtid_excuted表,这种情况适用于主库或者启用了二进制日志记录的从库。
如果MySQL Server意外停止,则当前日志文件中的GTID SET不会保存在mysql.gtid_excuted表中。
在MySQL Server重新启动期间,将扫描二进制文件并将这些GTID记录到表中(如果在MySQL Server重新启动时禁用了日志记录功能,
则在重新启动期间不会扫描二进制日志中的GTID,GTID记录无法被记录下来,也就无法启动复制),但不会记录所有已执行事务的GTID,
最后一个二进制日志文件中的GTID不会被记录。GTID的完整记录由系统变量gtid_excuted的全局值提供,始终使用全局变量@@gtid_excuted
(该变量在事务每次提交后更新)来表示MySQL Server的最新GTID状态,并且在查询全局变量时不会查询mysql.gtid_excuted表表中的数据。

mysql.gtid_executed 表如下所示:
mysql> select * from mysql.gtid_executed;
+--------------------------------------+----------------+--------------+
| source_uuid                          | interval_start | interval_end |
+--------------------------------------+----------------+--------------+
| d137d310-8161-11eb-afb6-000c29bdb102 |             39 |           39 |
| d137d310-8161-11eb-afb6-000c29bdb102 |             42 |           42 |
| d137d310-8161-11eb-afb6-000c29bdb102 |             43 |           43 |
| d137d310-8161-11eb-afb6-000c29bdb102 |             44 |           44 |
| d137d310-8161-11eb-afb6-000c29bdb102 |             47 |           47 |

为了节省空间,MySQL Server定期压缩mysql.gtid_executed表,将每个这样的行合并成一个GTID范围的单行,如下所示:
+--------------------------------------+----------------+--------------+
| source_uuid                          | interval_start | interval_end |
+--------------------------------------+----------------+--------------+
| d137d310-8161-11eb-afb6-000c29bdb102 |             39 |           47 |

通过设置系统变量 gtid_executed_compression_period 可以在执行压缩表之前控制表中允许记录的事务数,
即每隔多少个事务压缩一次,从而控制压缩率。此变量的默认值为1000,意味着默认情况下每1000个事务之后执行表的压缩。
设置gtid_executed_compression_period 为0可以防止执行压缩。如果要关闭压缩功能,请留意磁盘空间是否充足。
mysql.gtid_executed表的压缩由名为 thread/sql/compress_gtid_table 的专用前台线程执行。
可以通过 select * from performance_schema.threads where name like '%gtid%'\G  查看。
线程 thread/sql/compress_gtid_table 通常会休眠,直到执行了系统变量 gtid_executed_compression_period 定义的数据的事务时被唤醒,
如此无限重复循环。如果禁用二进制日志记录的同时将此值设置为0,则该线程用不会被唤醒。


GTID 的生命周期:
1. 当事务于主库执行时,系统会为事务分配一个由server uuid加序列号组成的GTID(当然读事务或者被主动过滤掉的事务不会被分配GTID),写binlog日志时此GTID标志着一个事务的开始。 
 
2. binlog中写GTID的event被称作Gtid_log_event,当binlog切换或者mysql服务关闭时,之前binlog中的所有gtid都会被加入mysql.gtid_executed表中。
 
3. 当GTID被分配且事务被提交后,他会被迅速的以一种外部的、非原子性的方式加入@@GLOBAL.gtid_executed参数中,
这个参数包含了所有被提交的GTID事务(其实他是一个GTID范围值,例如71cf4b9d-8343-11e8-97f1-a0d3c1f25190:1-10),
@@GLOBAL.gtid_executed也被用于主从复制,表示数据库当前已经执行到了哪个事务。
相比之下mysql.gtid_executed不能用于标识主库当前事务进度,毕竟他只有在binlog切换时才会将日志中的GTID加入(mysql服务关闭也相当于binlog切换)。
 
4. 在主从首次同步时(master_auto_position=1),slave会通过gtid协议将自己已经执行的gtid set(@@global.gtid_executed)发给master,
master比较后从首个未被执行的GTID事务开始主从同步。

5. 当事务随binlog被传输至slave后,slave每次读到Gtid_log_event就把自己的gtid_next参数设为此GTID,
需要注意的是这里的gtid_next是在复制进程的session context中自动设置的(由binlog提供的语句),
不同于show variables like 'gtid_next';这里看到的结果默认为AUTOMATIC,是当前会话本身的gtid_next,这是个session级别的参数。

6. 当开启并行复制时,slave会读取并检查事务的GTID确保当前GTID事务未被在slave执行过,且没有并行进程在读取并执行此事务,
如果有并行复制进程正在应用此事务那么slave server只会允许一个进程继续执行,@@GLOBAL.gtid_owned参数展示了当前哪个并行复制进程在执行什么事务。

7. 同样的,在slave上如果开启了binlog,GTID也会以Gtid_log_event事件写入binlog,同时binlog切换或者mysql服务关闭时,
当前binlog中的所有gtid都会被加入mysql.gtid_executed表中。

8. 在备库上如果未开启binlog,那么GTID会被直接持久化到mysql.gtid_executed表中,在这种情况下slave的mysql.gtid_executed表包含了所有已经被执行的事务。
需要注意的是在mysql5.7中,向mysql.gtid_executed表插入GTID的操作与DML操作是原子性的,对于DDL操作则不是,
因此如果slave在执行DDL操作的过程中异常中断那么GTID机制可能会失效。在mysql8.0中这个问题已经得到解决,DDL操作的GTID插入也是原子性的。

9. 同第3条中所说的一样,slave上的事务被执行后GTID也会被迅速的以一种外部的、非原子性的方式加入@@GLOBAL.gtid_executed参数中,
在slave的binlog未开启时mysql.gtid_executed中记载的已提交事务事实上与@@GLOBAL.gtid_executed记载的是一致的,
如果slave的binlog已开启那么mysql.gtid_executed的GTID事务集就没有@@GLOBAL.gtid_executed全了。
 

GTID的组成
GTID = server_uuid:transaction_id
server_uuid:在数据目录下的 auto.cnf 文件是用来保存 server_uuid的。MySQL启动时,会读取 auto.cnf 文件,
如果 auto.cnf 文件丢失,则生成一个新的 server_uuid,那么这样GTID的值也会改变。
transaction_id表示该实例上已经提交的事务数量,并且随着事务提交单调递增。
例如,要提交的第一个事务的 transaction_id 是1,第十个 transaction_id 是 10,在GTID中,transaction_id不可能是0。


GTID的好处
(1)更简单的实现failover ,主从环境主库的dump进程可以直接通过GTID定位到需要发送的binary log位置,而不再需要指定binary log的 LOG_FILE和LOG_POS,
只需指定主库的ip、port、username、password 就可以了,因为GTID的复制是自动的,所以会自动找到GTID去同步。根据GTID可以知道事务最初是在哪个实例上提交的。

(2)基于库的多线程复制,多线程是针对每个database开启相应的独立线程,即每个库有一个单独的sql thread,如果线上业务中,
只有一个database或者绝大多数压力集中在个别database的话,多线程并发复制特性就没有意义了(slave-parallel-workers=0 表示禁用多线程功能)。


GTID的限制
(1)不支持非事务引擎(从库报错,stop slave;start slave;忽略)。
(2)不支持create table xxx select 语句复制(在最新版本8.0.21已经解决了)。
(3)不允许一个SQL同时更新一个事务引擎和非事务引擎的表。
(4)在一个复制组中,必须要求统一开启GTID或者是关闭GTID。
(5)开启GTID需要重启(MySQL5.7.6版本开始可以在线升级gtid模式)。
(6)开启GTID后,就不再使用原来的传统的复制方式。
(7)对于create temporary table和drop temporary语句不支持。
(8)不支持sql_slave_skip_counter。


开启GTID
gtid_mode = on
log-bin = mysql-bin #5.6必选 5.7.5和之后可选,为了高可用,最好设置
log-slave-updates = 1 #5.6必选 5.7.5和之后可选,为了高可用,最好设置
enforce-gtid-consistency = 1


判断复制方式GTID 还是传统复制pos
Show slave status 查看Auto_Position字段。
0是传统复制pos方式。
1是gtid方式。


gtid_pureged
gtid_pureged 命令的实际意义:因为没有binlog信息(expire_logs_days),不考虑这些gtid确认和回滚。常用备份恢复,搭建从库的时候使用。
自动触发机制:flush,服务器重新启动
如果purged掉的GTIDs会包含到gtid_executed中。
mysqldump --set-gtid-purged=off/on 参数:是否将GTID_PURGED 添加到输出中。


gtid_pureged使用场景:
(1)在副本上禁用二进制日志记录提交的复制事务的GTIDs。
(2)写入二进制日志文件的事务的GTIDs,该文件现在已被清除。
(3)通过语句set @@GLOBAL.gtid_purged显式添加到集合中的gtid。


 

posted @ 2018-08-01 13:34  屠魔的少年  阅读(461)  评论(0)    收藏  举报