1.基本概念
MySQL 5.6 的新特性之一,全局事务标识符(GTID)是创建的唯一标识符,并与在源(主)服务器上提交的每个事务相关联。此标识符不但是唯一的,而且在给定复制设置中的所有服务器上都是唯一的。所有交易和所有GTID之间都有一对一的映射关系 。它由服务器ID以及事务ID组合而成。这个全局事务ID不仅仅在原始服务器上唯一,在所有存在主从关系 的mysql服务器上也是唯一的。正是因为这样一个特性使得mysql的主从复制变得更加简单,以及数据库一致性更可靠。一个GTID在一个服务器上只执行一次,避免重复执行导致数据混乱或者主从不一致。
657FDG47-4TSR-34QR-R4TZ-EG2WR435322:23
在传统的主从复制slave端,binlog是不用开启的,但是在GTID中slave端的binlog是必须开启的,目的是记录执行过的GTID(强制)。GTID用来代替classic的复制方法,不在使用binlog+pos开启复制。而是使用master_auto_postion=1的方式自动匹配GTID断点进行复制。
mysql的主从复制是十分经典的一个应用,但是主从之间总会有数据一致性(data consistency )的问题,一般情况从库会落后主库几个小时,而且在传统一主多从(mysql5.6之前)的模型中当master down掉后,我们不只是需要将一个slave提成master就可以,还要将其他slave的同步目的地从以前的master改成现在master,而且bin-log的序号和偏移量也要去查看,这是十分不方便和耗时的,但mysql5.6引入gtid之后解决了这个问题。
红色代表GTID,绿色代表传统主从:
2.gtid的生命周期
gtid的生命周期对于配置和维护基于gtid的复制至关重要。所以,请尽可能理解以下几个过程。
gtid在master和slave上是一直持久化保存(即使删除了日志,也会记录到Previous_GTID中)的。它在master和slave上的生命周期如下:
1.客户端发送DDL/DML给master上,master首先对此事务生成一个唯一的gtid,假如为uuid_xxx:1,然后立即执行该事务中的操作。
注意,主从复制的情况下,sync-binlog基本上都会设置为1,这表示在每次提交事务时将缓存中的binlog刷盘。所以,在事务提交前,gtid以及事务相关操作的信息都在缓存中,提交后它们才写入到binlog file中,然后才会被dump线程dump出去。
换句话说,只有提交了的事务,gtid和对应的事务操作才会记录到binlog文件中。记录的格式是先记录gtid,紧跟着再记录事务相关的操作。
2.当binlog传送到relay log中后,slave上的SQL线程首先读取该gtid,并设置变量 gtid_next 的值为该gtid,表示下一个要操作的事务是该gtid。 gtid_next 是基于会话的,不同会话的gtid_next不同。
3.随后slave检测该gtid在自己的binlog中是否存在。如果存在,则放弃此gtid事务;如果不存在,则将此gtid写入到自己的binlog中,然后立刻执行该事务,并在自己的binlog中记录该事务相关的操作。
注意,slave上replay的时候,gtid不是提交后才写到自己的binlog file的,而是判断gtid不存在后立即写入binlog file。
通过这种在执行事务前先检查并写gtid到binlog的机制,不仅可以保证当前会话在此之前没有执行过该事务,还能保证没有其他会话读取了该gtid却没有提交。因为如果其他会话读取了该gtid会立即写入到binlog(不管是否已经开始执行事务),所以当前会话总能读取到binlog中的该gtid,于是当前会话就会放弃该事务。总之,一个gtid事务是决不允许多次执行、多个会话并行执行的。
4.slave在重放relay log中的事务时,不会自己生成gtid,所以所有的slave(无论是何种方式的一主一从或一主多从复制架构)通过重放relay log中事务获取的gtid都来源于master,并永久保存在slave上。
使用xtrabackup备份的方式提供gtid复制的基准数据。其中涉及到一些gtid检查、设置的操作。通过这些操作,大概可以感受的到gtid复制的几个概念。
用一张图来说明:
3.基于gtid复制的好处
从上面可以看出,gtid复制的优点大致有:
1.保证同一个事务在某slave上绝对只执行一次,没有执行过的gtid事务总是会被执行。
2.不用像传统复制那样保证binlog的坐标准确,因为根本不需要binlog以及坐标。
3.故障转移到新的master的时候很方便,简化了很多任务。
4.很容易判断master和slave的数据是否一致。只要master上提交的事务在slave上也提交了,那么一定是一致的。
当然,MySQL提供了选项可以控制跳过某些gtid事务,防止slave第一次启动复制时执行master上的所有事务而导致耗时过久。
虽然对于row-based和statement-based的格式都能进行gtid复制,但建议采用row-based格式。
4.GTID的工作原理:
1、当一个事务在主库端执行并提交时,产生GTID,一同记录到binlog日志中。
2、binlog传输到slave,并存储到slave的relaylog后,读取这个GTID的这个值设置gtid_next变量,即告诉Slave,下一个要执行的GTID值。
3、sql线程从relay log中获取GTID,然后对比slave端的binlog是否有该GTID。
4、如果有记录,说明该GTID的事务已经执行,slave会忽略。
5、如果没有记录,slave就会执行该GTID事务,并记录该GTID到自身的binlog,在读取执行事务前会先检查其他session持有该GTID,确保不被重复执行。
6、在解析过程中会判断是否有主键,如果有就用二级索引,如果没有就用全部扫描。
5.GTID主从演示
5.1 环境说明
主机 | ip |
---|---|
master | 192.168.234.11 |
slave | 192.168.234.22 |
5.2配置
//修改 master 配置文件
[root@master ~]# vim /etc/my.cnf
log-bin=/var/lib/mysql/mysql-bin //这里目录属主和组必须是mysql
binlog_format=row
sync-binlog=1
server-id=101
master_info_repository=table
relay_log_info_repository=table
enforce_gtid_consistency=on //gtid复制需要加上的必须项
gtid_mode=on //gtid复制需要加上的必须项
//修改配置文件后重启 MySQL
[root@master ~]# service mysqld restart
//修改 slave 配置文件
[root@slave ~]# vim /etc/my.cnf
log-bin=/var/lib/mysql/slave-bin //5.6必须加,5.7可加可不加
binlog_format=row
relay-log=/var/lib/mysql/relay-bin
sync-binlog=1
server-id=182
master_info_repository=table
relay_log_info_repository=table
enforce_gtid_consistency=on //gtid复制需要加上的必须项
gtid_mode=on //gtid复制需要加上的必须项
//修改配置文件后重启 MySQL
[root@slave ~]# service mysqld restart
//master 上创建复制用户
mysql> create user 'wyz'@'192.168.234.%' identified by 'wyz123!';
Query OK, 0 rows affected (0.00 sec)
mysql> grant replication slave on *.* to 'wyz'@'192.168.234.%';
Query OK, 0 rows affected (0.00 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)
//因为master上的binlog日志没有删除过,所以在slave上直接change master to 配置连接参数。
mysql> CHANGE MASTER TO MASTER_HOST='192.168.234.11',MASTER_PORT=3306,MASTER_AUTO_POSITION=1;
Query OK, 0 rows affected (0.03 sec)
/启动slave上的两个复制线程
mysql> START SLAVE USER='wyz' PASSWORD='wyz123!';
Query OK, 0 rows affected, 1 warning (0.00 sec)
5.3验证
//查看io 线程和 sql 线程是否正常。
mysql> show processlist;
+----+-------------+-----------+------+---------+------+--------------------------------------------------------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+-------------+-----------+------+---------+------+--------------------------------------------------------+------------------+
| 2 | root | localhost | NULL | Query | 0 | starting | show processlist |
| 3 | system user | | NULL | Connect | 29 | Waiting for master to send event | NULL |
| 4 | system user | | NULL | Connect | 168 | Slave has read all relay log; waiting for more updates | NULL |
+----+-------------+-----------+------+---------+------+--------------------------------------------------------+------------------+
//在master上添加数据:
//创建myisam类型的数值辅助表和插入数据
mysql>CREATE TABLE num_isam (n INT NOT NULL PRIMARY KEY) ENGINE = MYISAM ;
mysql>DROP PROCEDURE IF EXISTS proc_num1;
DELIMITER $$
mysql>CREATE PROCEDURE proc_num1 (num INT)
BEGIN
DECLARE rn INT DEFAULT 1 ;
TRUNCATE TABLE backuptest.num_isam ;
INSERT INTO backuptest.num_isam VALUES(1) ;
dd: WHILE rn * 2 < num DO
BEGIN
INSERT INTO backuptest.num_isam
SELECT rn + n FROM backuptest.num_isam;
SET rn = rn * 2 ;
END ;
END WHILE dd;
INSERT INTO backuptest.num_isam
SELECT n + rn
FROM backuptest.num_isam
WHERE n + rn <= num;
END ;
$$
DELIMITER ;
# 创建innodb类型的数值辅助表和插入数据的存储过程
mysql>CREATE TABLE num_innodb (n INT NOT NULL PRIMARY KEY) ENGINE = INNODB ;
mysql>DROP PROCEDURE IF EXISTS proc_num2;
DELIMITER $$
mysql>CREATE PROCEDURE proc_num2 (num INT)
BEGIN
DECLARE rn INT DEFAULT 1 ;
TRUNCATE TABLE backuptest.num_innodb ;
INSERT INTO backuptest.num_innodb VALUES(1) ;
dd: WHILE rn * 2 < num DO
BEGIN
INSERT INTO backuptest.num_innodb
SELECT rn + n FROM backuptest.num_innodb;
SET rn = rn * 2 ;
END ;
END WHILE dd;
INSERT INTO backuptest.num_innodb
SELECT n + rn
FROM backuptest.num_innodb
WHERE n + rn <= num ;
END ;
$$
DELIMITER ;
# 分别向两个数值辅助表中插入100W条数据
mysql>CALL proc_num1 (1000000) ;
mysql>CALL proc_num2 (1000000) ;
//在 slave 上查看 slave 的状态,以下是同步结束后的状态信息:
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.234.11
Master_User: wyz
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000002
Read_Master_Log_Pos: 10057531
Relay_Log_File: relay-bin.000001
Relay_Log_Pos: 10058590
Relay_Master_Log_File: mysql-bin.000002
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 10057531
Relay_Log_Space: 10057648
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 101
Master_UUID: reta842t-5846-fe82-ge87-egz2784tjgo
Master_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set: reta842t-5846-fe82-ge87-egz2784tjgo5:1-55
Executed_Gtid_Set: reta842t-5846-fe82-ge87-egz2784tjgo5:1-55
Auto_Position: 1
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
1 row in set (0.00 sec)
```c