MySQL 5.7中新增的表gtid_executed,看看是否解决了你的痛点
前言
由Inside君亲授的上海MySQL周末线下VIP培训班还有2周即将开始,目前仅剩2个名额。Inside君致力于打造最好的MySQL培训班,咨询请加Inside君个人公众账号:82946772。更多详情请点击:只做最好的MySQL数据库培训——3月上海周末线下班开始报名 。北京的同学们,也不要急,因为Inside君马上要来北京了,本周确定培训地点就会发布相关培训信息(突然好想唱下面这首歌)。
正文
MySQL 5.6版本开启GTID模式,必须打开参数log_slave_updates,简单来说就是必须在从机上再记录一份二进制日志。这样的无论对性能还是存储的开销,无疑会相应的增大。而MySQL 5.7版本开始无需在GTID模式下启用参数log_slave_updates,其中最重要的原因在于5.7在mysql库下引入了新的表gtid_executed,其表结构如下所示:
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)
简单来说,该表会记录当前执行的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位置信息。当然,如果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位置。
开启log_slave_updates后,主从服务器性能有多少的差异,这是一个蛮有意思的问题。不过IMG社区群中的西毒同学,已经发现了另一个有意思的问题,即主服务器将innodb_flush_log_at_trx_commit设置为0,依然能保证crash safe。有同学能想到原因是什么吗?欢迎前来IMG微信群进行讨论
历史阅读