Mysql relication有两种方式,一种是复制主库二进制日志中的事件(对于mysql来说,每一次数据改变在二进制日志中,就是一个events),另一种是基于GTIDs(global transaction identifiers)。

1.基于二进制日志位置的复制

大概步骤:
1/主库需要开启bin log ,设置一个server id
2/从库需要配置一个server id
3/创建一个用户用来同步日志
4/记录当前二进制日志中的position号,从库需要知道从哪个位置开始同步
5/同步数据
6/配置从库连接到主库

详细步骤:

1.主库设置serverid,开启binlog

  1. [mysqld]
  2. log-bin=mysql-bin
  3. server-id=250

在主从复制中,每个服务器都需要一个唯一的server-id。server-id的取值范围是1-2^32-1。
可以动态的修改server-id:

SET GLOBAL server_id = 2;

默认情况是0,不允许任何复制库连接到源库。如果之前将server-id设置为0,设置成其他的值时需要重启服务器才能生效。如果之前binlog没有打开,打开binlog时也需要重启服务器。
为了保证复制的持久性与一致性,也需要设置以下两个参数:

[mysqld]
innodb_flush_log_at_trx_commit=1
sync_binlog=1

2.主库创建一个replica用户

mysql> CREATE USER 'repl'@'%' IDENTIFIED BY 'Mysql5.7';
mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';

当使用CHANGE MASTER TO 语句时会用到该用户,只要给用户赋予了REPLICATION SLAVE权限,任何用户都能用来完成CHANGE MASTER TO操作。

3.获得主库的二进制日志信息

mysql> show master status;
+----------------+----------+--------------+------------------+-------------------+
| File           | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+----------------+----------+--------------+------------------+-------------------+
| centos7.000030 |     1106 |              |                  |                   |
+----------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)

如果在搭建过程中关闭了主库,则这一步可以跳过,因为重启时会生成新的二进制文件。如果没有关闭主库,需要确认当前主库使用的的二进制日志的相关信息,以便稍后同步时需要从哪个位置点开始同步。如果之前没有开启binlog,稍后同步的文件名需要使用’’,position使用4。

4.复制数据到从库

shell> mysqldump -A --master-data --single-transaction > dbdump.db
scp
shell> mysql </data/dbdump.db

使用mysqldum备份数据,并传送到从库,然后恢复。如果是一个空的数据库,则这一步可以省略。

5.从库配置

SET GLOBAL server_id = 230;

从库上的bin-log可以开,也可以不开。不开是因为用不到,开是因为可以用来备份,用来做崩溃恢复。如果想要复杂的主从结构,例如多级主从,则需要开启bin-log。

mysql> CHANGE MASTER TO
    -> MASTER_HOST='192.168.20.250',
    -> MASTER_USER='repl',
    -> MASTER_PASSWORD='Mysql5.7',
    -> MASTER_LOG_FILE='centos7.000030',
    -> MASTER_LOG_POS=1106;
Query OK, 0 rows affected, 2 warnings (0.00 sec)

因为备份时使用了—master-data,所以MASTER_LOG_FILE,MASTER_LOG_POS可以从备份中获取。

mysql> START SLAVE;

最后开启同步线程即可。

6.验证

mysql> show slave status\G;
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.20.250
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: centos7.000030
          Read_Master_Log_Pos: 1365
               Relay_Log_File: test01-relay-bin.000002
                Relay_Log_Pos: 577
        Relay_Master_Log_File: centos7.000030
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB:
          Replicate_Ignore_DB:

Slave_IO_Running: Yes
Slave_SQL_Running: Yes
这两个字段的值为yes表明同步成功。

2.在已有的主从复制结构中增加一个从库

大概步骤:
1/停止从库复制线程,并记录当前从库使用的bin-log以及relay-log

mysql> STOP SLAVE;
mysql> SHOW SLAVE STATUS\G

2/关闭从库

shell> mysqladmin shutdown

3/复制当前从库的数据目录到新的从库,包括bin-log与relay 日志文件,复制完之后要删除auto.cnf文件,这样新从库启动时会生成新的UUID。由于主机名可能不同,relay-log的名称也会变化,所以新的从库上的relay-log名称也要与之前的从库的relay-log名称相同。
4/启动老的从库
5/启动新的从库

3.基于GTIDs的复制

1.关于GTID

在mysql中,每个事务都会有自己全局唯一的事务id。每开启一个事务就会分配一个GTID,无论这个事务是否已经执行,或者记录到bin-log。mysql.gtid_executed记录了分配的事务id,但是不包括那些在当前二进制日志文件中的事务。如果有一个会话分配了一个GTID开启了一个事务,但是没有提交或者回滚,使用相同GTID的并发事务将会被阻塞。如果事务提交了,那么并发事务的GTID将跳过当前的GTID,如果事务回退了,并发事务将使用该GTID。
GTID一般由两部分组成

GTID = source_id:transaction_id

sourceid一般是server_uuid。当服务器启动时会去/data_dir_/auto.cnf中读取uuid的值。如果启动时没有auto.cnf,mysql会自动生成一个,不要尝试去改这个文件。

[root@test01 data]# cat auto.cnf
[auto]
server-uuid=624a4fe7-d4bc-11eb-a81f-0050562f01a6

事务id的最大值是2^63 - 1,等于9,223,372,036,854,775,807。如果事务的id超过了这个值, 根据binlog_error_action变量mysql做出相应的动作,默认是shutdown。

GTID集合
同一个服务器上的GTID范围可以用一句话来表示

3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5

在同一个服务器上的单个GTID跟范围GTIDs也可以用一句话来表示

3E11FA47-71CA-11E1-9E33-C80AA9429562:1-3:11:47-49

不同的服务器上的GTID与范围GTIDs也可以用一句话来表示,这个通常出现在多主的情况。

2174B383-5441-11E8-B90A-C80AA9429562:1-3, 24DA167-0C0C-11E8-8442-00059A3C7B00:1-19

mysql.gtid_executed (只有开启gtid,这张表才有记录)

mysql> desc gtid_executed;
+----------------+------------+------+-----+---------+-------+
| Field          | Type       | Null | Key | Default | Extra |
+----------------+------------+------+-----+---------+-------+
| source_uuid    | char(36)   | NO   | PRI | NULL    |       |
| interval_start | bigint(20) | NO   | PRI | NULL    |       |
| interval_end   | bigint(20) | NO   |     | NULL    |       |
+----------------+------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

对于GTID集合,start表示开始,end表示结束,如果是单个GTID,则start与end是相同的。使用RESET MASTER会清空这张表。
通常情况,由同一个服务器产生的独立GTIDs都是一行一行填充到表里。为了节省空间,mysql会周期性的自动压缩这些GTIDs。

+--------------------------------------+----------------+--------------+
| 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           |
...

使用GTID_EXECUTED_COMPRESSION_PERIOD系统变量可以控制执行多少个事务后压缩一次,默认是1000。如果这个变量的值为0,就意味着永不压缩,这会导致表的增长。但是,当开启bin-log时,GTID_EXECUTED_COMPRESSION_PERIOD这个变量将无效,当每次bin-log切换时,mysql就会自动压缩一次。

2.GTID的生命周期(复制原理)

1/当主从复制的主库执行并提交了一个事务,这个事务将会被分配一个GTID。在bin-log日志文件里记录这个GTID,然后才写事务。如果客户端事务没有写进bin-log(因为过滤掉了,或者事务只读),将不会分配GTID
2/把bin-log传输到从库,并应用成realy-log后,从库读取GTID的值并付给gtid_next系统变量。
3/在执行GTID的事务之前,从库会验证是否这个GTID已经应用到从库,如果应用了,那就跳过。还会验证这个GTID是否正在由其他会话执行,但是还没提交。gtid_owned系统变量@@global.gtid_owned可以查看当前正在使用的gtid以及使用它的线程。
4/如果GTID没有执行过,从库将该事务应用到从库。因为已经把主库的gtid赋值给了从库上的gtid_next,所以从库无需生成新的gtid。
5/如果从库开启了bin-log,会把GTID与事务一起写到bin-log中。并把之前写到bin-log中的GTID写到mysql.gtid_executed表中。
6/如果从库禁用了bin-log,mysql会直接将GTID写到mysql.gtid_executed表中。mysql会追加一个插入到mysql.gtid_executed的语句到事务中。

什么样的变化会分配一个GTID
每一个DDL,DML写到bin-log的事务都会分配一个GTID。数据库的创建,修改,删除也会分配一个GTID。其他数据库对象的创建,修改,删除,例如,存储过程,函数,视图,触发器,事件,角色,grant语句。

当一个表被自动删除,且被记录到bin-log,也会分配一个GTID。当从库应用主库的日志之后,临时表会被自动删除。如果事务没被写到bin-log,那么就不会分配一个GTID,包括rollback,或者禁用bin-log(使用—skip-log-bin,或者set @@session.sql_log_bin=0)

当一个语句包含多个事务时,会生成多个GTID。例如一个存储过程,当使用binlog_format=row 格式时,create table … select 语句,create 语句会生成一个GTID,insert也会生成一个GTID。

gtid_next的默认值是automatic,当事务提交且写到bin-log中时会自动生成一个GTID。当没有使用默认值并且给了gtid_next一个有效的值时,服务器给该事务分配这个指定的值,即使这个事务回退了或者没有写到bin-log或者这个事务是空的,也会将gtid_next的值给到gtid_executed。当给一个事务指定的gtid之后,无论是提交还是回退,在进行下一个事务之前都需要设置 SET @@session.gtid_next,或者将gtid_next的值改为automatic。

gtid_purged系统变量(@@global.gtid_purged)记录了那些已经提交但是没有记录到bin-log的事务。当把事务记录到gtid_purged时,也会把该事务记录到gtid_executed。gtid_purged是gtid_executed的子集。
记录到gtid_purged的几种情况:
1/从库没有开启bin-log,应用事务commit之后会记录到gtid_purged
2/记录到bin-log的事务被清空了
3/显示的设置 SET @@GLOBAL.gtid_purged

如果需要重置gtid的执行历史,可以使用RESET MASTER。当执行RESET MASTER之后,会发生以下变化:
1/gtid_purged的值会变成空字符’ ‘
2/gtid_executed的全局变量(不是会话变量)会变成空字符
3/mysql.gtid_executed表会被清空
4/如果开启了binlog,之前存在的bin-log日志以及bin-log index将会被删除。

3.搭建基于GTID的主从

主要步骤:
1/如果使用基于位置的主从已经在运行了,设置他们只读,如果主从还没有设置,直接跳到第三步
2/关闭所有服务
3/打开GTID与其他选项并重启mysql
4/指定从库使用复制源,并使用自动定位
5/使用一个新的备份,没有开启gtid的bin-log是不能用到开启bin-log的的服务器上
6/开启复制,关闭只读模式

详细步骤:
1/设置只读:

mysql> SET @@GLOBAL.read_only = ON;

2/关闭所有mysql

shell> mysqladmin -uroot -p shutdown

3/设置所有服务器参数,并启动

gtid_mode=ON
enforce-gtid-consistency=ON

4/配置从库使用自动定位

mysql> CHANGE MASTER TO
    -> MASTER_HOST='192.168.20.250',
    -> MASTER_USER='repl',
    -> MASTER_PASSWORD='Mysql5.7',
    -> MASTER_AUTO_POSITION=1;
Query OK, 0 rows affected, 2 warnings (0.00 sec)

5/备份
因为之前的备份都是没有使用gtid的,在打开了gtid之后,没有gtid的备份将不能被使用。

shell> mysqldump -A --master-data --single-transaction > dbdump.db

6/开启主从同步

mysql> START SLAVE;

7/验证
Retrieved_Gtid_Set ,Executed_Gtid_Set 这两个表示接受到的gtid与已执行的gtid

mysql> show slave status\G;
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.20.250
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: centos7.000032
          Read_Master_Log_Pos: 513
               Relay_Log_File: test01-relay-bin.000002
                Relay_Log_Pos: 722
        Relay_Master_Log_File: centos7.000032
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
...
...
           Retrieved_Gtid_Set: d23d9c28-3515-11eb-82a4-000c2917db06:1
            Executed_Gtid_Set: d23d9c28-3515-11eb-82a4-000c2917db06:1
                Auto_Position: 1
         Replicate_Rewrite_DB:
                 Channel_Name:
           Master_TLS_Version:
1 row in set (0.00 sec)

4.使用GTID复制的一些限制

1/非事务性的存储引擎不能像事务性的存储引擎那样使用同一个语句或者事务。在同一个事务中使用不同存储引擎会产生多个GTID。这种问题也会发生在主库与从库相同的表使用了不同的存储引擎。
2/CREATE TABLE ….. SELECT 语句。当binlog-format=statement时,这个语句会被记录到bin-log中当成一个事务,且分配一个GTID,当binlog-format=row时,这个语句会被分配两个GTID。如果主库使用了STATEMENT,从库使用了row,那么这条语句可能会有问题。
3/当使用GTID时,enforce_gtid_consistency=on,不能在事务,存储过程,触发器中创建,删除临时表。只有在事务外,且autocommit=1时,才能在打开gtid的时候创建,删除临时表。
4/不能使用sql_slave_skip_counter,跳过某个事务,但是可以使用空事务来跳过事务。

5.关于GTID的函数

GTID_SUBSET(set1,set2) 给定两个关于GTID的集合,如果set1在set2中则返回true,否则false
GTID_SUBTRACT(set1,set2)给定两个关于GTID的集合,返回只在set1中但是不在set2中的GTID
WAIT_FOR_EXECUTED_GTID_SET(set1[,timeout])等待给定的set集合应用所有的事务再进行其他操作。timeout是指在多少秒之后停止等待。