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。