MySQL 使用GTID进行复制

1. GTID的格式和存储

GTID即全局事务ID(global transaction identifier),GTID实际上是由server_uuid:transaction_id组成的。其中server_uuid是一个MySQL实例的唯一标识,存放在数据目录的auto.cnf文件下,transaction_id代表了该实例上已经提交的事务数量,并且随着事务提交单调递增,所以GTID能够保证每个MySQL实例事务的执行(不会重复执行同一个事务,并且会补全没有执行的事务)。

1.1 GTID 集

GTID集是一组全局事务标识符,如下所示:

gtid_set:
    uuid_set [, uuid_set] ...
    | ''

uuid_set:
    uuid:interval[:interval]...

uuid:
    hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh

h:
    [0-9|A-F]

interval:
    n[-n]

    (n >= 1)

GTID集在MySQL服务器中以多种方式使用。 例如,gtid_executedgtid_purged系统变量存储的值表示为GTID集。 此外,函数GTID_SUBSET()GTID_SUBTRACT()需要GTID集作为输入。 当从服务器变量返回GTID集时,UUID按字母顺序排列,数值间隔按升序合并。

1.2 mysql.gtid_executed 表

GTID存储在mysql数据库中名为gtid_executed的表中。 对于它表示的每个GTID或GTID集合,该表中的一行包含原始服务器的UUID,以及该集合的起始和结束事务ID; 对于仅引用单个GTID的行,这两个最后两个值是相同的。

当slave禁用binlog时,mysql.gtid_executed表使slave能够使用GTID,并且它可以在二进制日志丢失时保留GTID历史记录。

当gtid_mode为ON或ON_PERMISSIVE时,GTID仅存储在mysql.gtid_executed表中,存储GTID的位置取决于是启用还是禁用binlog:

  • 如果禁用二进制日志记录(log_bin为OFF),或者如果禁用log_slave_updates,则服务器将属于每个事务的GTID与表中的事务一起存储。 此外,该表可以定期压缩; 此情况仅不适用于复制中的master,因为在主服务器上,必须启用二进制日志记录才能进行复制。

  • 如果启用了二进制日志记录(log_bin为ON),则无论何时轮询二进制日志或关闭服务器,服务器都会将写入先前二进制日志的所有事务的GTID写入mysql.gtid_executed表。 这种情况适用于复制主服务器或启用了二进制日志记录的复制从服务器。

    如果服务器意外停止,则当前二进制日志中的GTID集不会保存在mysql.gtid_executed表中。 在这种情况下,这些GTID会在恢复期间添加到表和gtid_executed系统变量中的GTID集合中。

    启用二进制日志记录时,mysql.gtid_executed表不会为所有已执行的事务提供GTID的完整记录。 该信息由gtid_executed系统变量的全局值提供。

命令RESET MASTER将重置mysql.gtid_executed表。

1.3 mysql.gtid_executed 表压缩

启用GTID时,服务器会定期在mysql.gtid_executed表上执行此类压缩。 通过设置gtid_executed_compression_period系统变量,您可以控制压缩表之前允许的事务数,从而控制压缩率。 该变量的默认值为1000; 这意味着,默认情况下,在每1000次事务之后执行表的压缩。 将gtid_executed_compression_period设置为0可以防止执行压缩; 但是,如果执行此操作,您应该为gtid_executed表可能需要的磁盘空间量的大幅增加做好准备。

【注意】:
启用binlog时,且不使用gtid_executed_compression_period的值,会在每个binlog轮换时压缩mysql.gtid_executed表。

压缩前:

mysql> SELECT * FROM mysql.gtid_executed;
+--------------------------------------+----------------+--------------+
| source_uuid                          | interval_start | interval_end |
|--------------------------------------+----------------+--------------|
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 37             | 37           |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 38             | 38           |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 39             | 39           |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 40             | 40           |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 41             | 41           |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 42             | 42           |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 43             | 43           |
...

压缩后:

+--------------------------------------+----------------+--------------+
| source_uuid                          | interval_start | interval_end |
|--------------------------------------+----------------+--------------|
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 37             | 43           |
...

mysql.gtid_executed表的压缩由名为thread/sql/compress_gtid_table的专用前台线程执行。 该线程未在SHOW PROCESSLIST的输出中列出,但可以将其视为performance_schema.threads表中的一行,如下所示:

mysql> SELECT * FROM performance_schema.threads WHERE NAME LIKE '%gtid%'\G
*************************** 1. row ***************************
          THREAD_ID: 26
               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: 1509
  PROCESSLIST_STATE: Suspending
   PROCESSLIST_INFO: NULL
   PARENT_THREAD_ID: 1
               ROLE: NULL
       INSTRUMENTED: YES
            HISTORY: YES
    CONNECTION_TYPE: NULL
       THREAD_OS_ID: 18677

thread/sql/compress_gtid_table线程通常会休眠,直到执行了gtid_executed_compression_period事务,然后唤醒以执行mysql.gtid_executed表的压缩,如前所述。 然后它休眠直到另一个gtid_executed_compression_period事务发生,然后唤醒再次执行压缩,无限期地重复此循环。 禁用二进制日志记录时将此值设置为0意味着线程始终处于休眠状态且从不唤醒。

2. GTID 生命周期

GTID的生命周期包括以下步骤:

  1. Master产生GTID:在Master端产生一个GTID的信息,并保存到binlog中。
  2. 发送Binlog信息到从库上,将binlog信息发送到SLAVE所在的服务器上。将SLAVE中的gtid_next的值设置为GTID的值。
  3. SLAVE执行GTID。SLAVE首先验证是否在自己的二进制日志中使用了这个GTID号
  4. SLAVE不生成GTID。由于GTID不为空,SLAVE不会尝试为该事务生成新的GTID,而是从gtid_next 中读取GTID值,写入二进制日志中,来标识一个事务的GTID的值。

gtid_purged
gtid_purged系统变量(@@global.gtid_purged)中的GTID集包含已在服务器上提交但在服务器上的任何二进制日志文件中不存在的所有事务的GTID。 以下类别的GTID在此集合中:

  • 在slave上禁用binlog提交的复制事务的GTID
  • 已写入已清除的binlog的事务的GTID
  • 由语句SET @@global.gtid_purged明确添加到集合中的GTID

服务器启动时,将初始化gtid_purged系统变量中的GTID集。 每个二进制日志文件都以事件Previous_gtids_log_event开头,该事件包含所有先前二进制日志文件中的GTID集(由前一个文件的Previous_gtids_log_event中的GTID和文件本身中每个Gtid_log_event的GTID组成)。 最旧的二进制日志文件中的Previous_gtids_log_event的内容用于在服务器启动时初始化gtid_purged集,并在清除二进制日志文件时维护该集。

3. 使用GTID搭建主从

GTID使用master_auto_position=1代替了基于binlog和position号的主从复制搭建方式,更便于主从复制的搭建。

3.1 环境准备

类型 ip prot server-id 是否开启binlog binlog格式 log_slave_updates参数
master 192.168.56.100 3307 1003307 log-bin = /data/mysql/mysql3307/logs/my3307_binlog binlog_format = row log_slave_updates=1
slave 192.168.56.200 3307 2003307 log-bin = /data/mysql/mysql3307/logs/my3307_binlog binlog_format = row log_slave_updates=1

3.2 配置GTID主从的参数

  • server_id: 设置MySQL实例的server_id,每个server_id不能一样
  • gtid_mode=ON: MySQL实例开启GTID模式
  • enforce_gtid_consitency=ON:使用GTID模式复制时,需要开启参数,用来保证数据的一致性。
  • log-bin: MySQL必须要开启binlog
  • log-slave-updates=1:决定SLAVE从Master接收到更新且执行是否记录到SLAVE的binlog中
  • binlog_format=ROW: binlog格式为row
  • skip-slave-start=1(可选): 当SLAVE数据库启动的时候,SLAVE不会启动复制

3.3 在master上操作

1) 创建复制账号

create user 'repl'@'%' identified by 'wanbin';

grant replication slave on *.* to 'repl'@'%';

2) master数据库利用xtrabackup备份至slave上


innobackupex --defaults-file=/etc/my3307.cnf -uroot -pmysql --stream=tar ./ |ssh root@mysqldb2 "cat - > /data/backup/dbback`date +%Y%m%d_%H%M%S`.tar"

3.4 在slave上操作

  1. 解压备份
mkdir dbback20181012_151213

tar -xvf dbback20181012_151213.tar -C dbback20181012_151213
  1. prepare备份
innobackupex --apply-log /data/backup/dbback20181012_151213/
  1. 恢复备份
innobackupex --defaults-file=/etc/my3307.cnf --copy-back /data/backup/dbback20181012_151213/

chown -R mysql:mysql /data/mysql/mysql3307/data

mysqld --defaults-file=/etc/my3307.cnf &
  1. 过滤掉已执行过的gtid
1)查看xtrabackup_info文件中的gtid信息
cat xtrabackup_info|grep binlog_pos
binlog_pos = filename 'my3307_binlog.000002', position '2401', GTID of the last change '3a068bf8-cdeb-11e8-8176-080027b0b461:1-10'

2)查看slave已执行的gtid是否为空,如果不为空,需要执行reset MASTER进行清理,否则无法设置gtid。
root@localhost [(none)] 15:50:41>show master status \G;
*************************** 1. row ***************************
             File: my3307_binlog.000002
         Position: 234
     Binlog_Do_DB: 
 Binlog_Ignore_DB: 
Executed_Gtid_Set: 3a068bf8-cdeb-11e8-8176-080027b0b461:1-10,
ffe86a27-cdef-11e8-bb92-0800275b8a9a:1-2
1 row in set (0.00 sec)

3)执行reset master
root@localhost [(none)] 15:50:52>reset master;
Query OK, 0 rows affected (0.04 sec)

root@localhost [(none)] 15:53:18>show master status\G
*************************** 1. row ***************************
             File: my3307_binlog.000001
         Position: 154
     Binlog_Do_DB: 
 Binlog_Ignore_DB: 
Executed_Gtid_Set: 
1 row in set (0.00 sec)

4)执行GTID_PURGED
root@localhost [(none)] 15:56:32>set session sql_log_bin = 0;
root@localhost [(none)] 15:56:53>set global gtid_purged='3a068bf8-cdeb-11e8-8176-080027b0b461:1-10';
root@localhost [(none)] 15:57:58>set session sql_log_bin = 1;

  1. 配置主从
执行help change master to 可以获取change master to命令

CHANGE MASTER TO
  MASTER_HOST='192.168.56.100',
  MASTER_USER='repl',
  MASTER_PASSWORD='wanbin',
  MASTER_PORT=3307,
  master_auto_position=1;
  1. 开始主从复制
start slave;


#如果想分别指定启动线程,可以使用如下命令
START SLAVE IO_THREAD;
START SLAVE SQL_THREAD;

#同样关闭命令:
STOP SLAVE;

#分别关闭命令:
STOP SLAVE IO_THREAD;
STOP SLAVE SQL_THREAD;

4. 使用gtid进行复制的限制

由于基于GTID的复制依赖于事务,因此在使用时不支持MySQL中可用的某些功能。本节提供有关使用GTID进行复制的限制和限制的信息。

4.1 非事务性存储引擎的更新

使用GTID时,使用非事务性存储引擎(如MyISAM)对表的更新不能在与使用事务性存储引擎(如InnoDB)的表的更新相同的语句或事务中进行。

此限制是由于对使用非事务性存储引擎的表的更新与对同一事务中使用事务存储引擎的表的更新混合可能导致将多个GTID分配给同一事务。

当master和slave使用不同的存储引擎用于同一个表的相应版本时,也会发生这样的问题,其中一个存储引擎是事务性的而另一个不是。 还要注意,定义为在非事务性表上运行的触发器可能是导致这些问题的原因。

在刚刚提到的任何一种情况下,事务和GTID之间的一对一对应关系被破坏,结果是基于GTID的复制无法正常运行。

4.2 CREATE TABLE … SELECT 语句

CREATE TABLE … SELECT对于基于语句的复制是不安全的。 使用基于行的复制时,此语句实际上记录为两个单独的事件 - 一个用于创建表,另一个用于将源表中的行插入刚刚创建的新表中。 当在事务中执行此语句时,在某些情况下,这两个事件可能会接收相同的事务标识符,这意味着slave将跳过包含插入的事务。 因此,使用基于GTID的复制时不支持CREATE TABLE … SELECT。

4.3 临时表

使用GTID时(即,enforce_gtid_consistency系统变量设置为ON时),事务,过程,函数和触发器内不支持CREATE TEMPORARY TABLE和DROP TEMPORARY TABLE语句。 可以在启用GTID的情况下使用这些语句,但仅限于任何事务之外,并且仅使用autocommit = 1。

4.4 防止执行不受支持的语句

要防止执行会导致基于GTID的复制失败的语句,必须在启用GTID时使用–enforce-gtid-consistency选项启动所有服务器。

4.5 跳过事务

使用GTID时不支持sql_slave_skip_counter。 如果您需要跳过事务,请使用master的gtid_executed变量的值; 有关详细信息,请参考
https://dev.mysql.com/doc/refman/5.7/en/replication-gtids-failover.html#replication-gtids-failover-empty

4.6 Ignoring servers

使用GTID时,不推荐使用CHANGE MASTER TO语句的IGNORE_SERVER_IDS选项,因为已经应用的事务会自动被忽略。 在启动基于GTID的复制之前,请检查并清除之前在相关服务器上设置的所有忽略的服务器ID列表。 可以为各个通道发出的SHOW_SLAVE_STATUS语句显示已忽略的服务器ID列表(如果有)。 如果没有列表,则Replicate_Ignore_Server_Ids字段为空。

4.7 GTID mode and mysqldump

如果目标服务器的二进制日志中没有GTID,则可以将使用mysqldump创建的转储导入到启用了GTID模式的MySQL服务器中。

4.8 GTID mode and mysql_upgrade

当服务器在启用全局事务标识符(GTID)的情况下运行时(gtid_mode = ON),请不要通过mysql_upgrade启用二进制日志记录(–write-binlog选项)。

posted @ 2018-10-12 16:03  DB-Engineer  阅读(3013)  评论(0编辑  收藏  举报