有关MySQL组复制的事务一致性参数理解

一、简介

 从mysql 8.0.14开始,组复制引进一个新的参数,group_replication_consistency,事务一致性配置参数。用以控制在组复制中的事务一致性等级,其有5种可选配置项:

EVENTUAL(默认)

BEFORE_ON_PRIMARY_FAILOVER

BEFORE

AFTER

BEFORE_AND_AFTER

 我们知道组复制是基于paxos一致性算法保证了最终一致性。但是有时某些事务需要强一致,我们必须确保事务提交成功后,其修改在其他节点(非事务执行节点)上也能马上看到;亦或者在读写分离情况下,从库的只读事务能读取到最新的事务。

 而mysql 8.0.14引入的事务一致性配置参数group_replication_consistency就是用于配置事务一致性位点的参数,以解决上述各种问题。下面我们首先简单说明一下组复制原理,然后一致性参数各选项的含义,以及实现。

二、选项分析

2.1 组复制原理

 大家都知道了MySQL复制的原理,这里简单说明一下组复制的实现流程原理,主要理解组复制能更好理解事务一致性等级参数。下面是官方组复制原理图。

组复制原理

 组复制采用了乐观事务模型,事务可以在相应的服务器上先执行而无需与其他节点协商,直到提交阶段,才发送给组内的其他节点进行冲突检测,协商是否可以提交。如果可以则提交,否则就回滚。其中协商,冲突检测,事务原子广播都是在组复制插件中完成。由于各节点最终执行的事务以及顺序都一致,从而保证了数据的最终一致性。

 大致理解了组复制的原理,下面讲解group_replication_consistency各选项的含义,以及内部实现方式,为了简化作图,后续图中的commit包含上图从consensus到commit的所有操作

2.2 各选项含义

注意:下面使用都是在单主模式下进行的

  • EVENTUAL

 这是参数group_replication_consistency的默认值,也是8.0.14之前的一致性方式。在该配置下,RO和RW事务都不需要等待之前的事务完成。表现如下:

会话1(主,EVENTUAL级别) 会话2(从,EVENTUAL级别)
lock table t1 read;
insert into t1 values (*); //执行成功,不会产生等待
select * from t1; //执行成功,返回结果为插入前的状态
unlock tables; //执行成功。此时Primary 上的insert 执行成功
select * from t1; //执行成功,返回结果为插入后的状态

 可以看到:尽管会话2锁住了表t1,对于主库t1表的修改也能进行,无影响;并且③执行查询也能成功,返回旧的结果集。内部图如下:

EVENTUAL

逻辑如下:

1、在服务器M1上开始了事务T1;

2、执行到提交阶段,将事务分发到各个节点(包括自己)

3、各节点冲突检测:如果存在冲突则回滚;如果没有冲突,M1上事务进行提交,其他节点上放入relaylog,执行提交

4、M3上,在事务T1传送到服务器之前开启了事务T2,从而事务T2在T1前被执行,从而意味着T2不能看到T1的数据变化。

  • BEFORE_ON_PRIMARY_FAILOVER

 该一致性级别仅仅影响新主的读写。新主在应用relaylog中积累的旧主事务之前,所有事务(RO和RW)都会被阻塞。这确保在发生切换(不管有意还是无意)时,新主上总能看到最新的值。这保证了一致性,但不可避免增加新主处理积累事务的延迟。

  • BEFOR

 事务在执行之前等待所有先前的事务完成。这确保了事务执行在最新的数据之上,而不管这个事务在哪个服务器上执行。该级别就是可以避免在从库上的非一致性读(包含BEFORE_ON_PRIMARY_FAILOVER级别)。实验如下:

会话1(主,EVENTUAL级别) 会话2(从)
set @@session.group_replication_consistency='BEFORE' ;
lock table t1 read;
insert into t1 values (*); //执行成功,不会产生等待
select * from t1; //阻塞,如果超过wait_timeout设置的时间,报错:ERROR: 3797: Error while waiting for group transactions commit on group_replication_consistency= 'BEFORE'
unlock tables; //执行成功。此时Primary 上的insert 执行成功
select * from t1; //执行成功,返回结果为插入后的状态

 实现逻辑如下:

BEFOR

1、事务T1的执行逻辑不变;

2、在BEFOR一致性级别下,服务器M3开始事务T2,它将发送一个消息给组内所有成员(包含自己),该信息在事务T2执行前为其提供全局序列信息。

3、当大多数节点的回复信息被收到时,M3将会获得RECEIVED_TRANSACTION_SET事务GTID集合,该集合包含T2前组中所有需要提交的事务。

4、事务T2等待RECEIVED_TRANSACTION_SET事务集合中的所有事务都完成提交,才开始执行,后续步骤与一般提交一致。从而确保了事务T2读取最新的数据。

  • AFTER

 RW事务需要等待其在所有节点上的变更结束,才能执行成功。“AFTER”不影响RO事务。该级别确保了但一个读写事务在本地提交成功后,后续的操作不管在哪个节点都能看到最新的数据—读写事务修改的数据(包含BEFORE_ON_PRIMARY_FAILOVER级别)。实验如下:

会话1(主,AFTER级别) 会话2(从,EVENTUAL级别)
lock table t1 read;
insert into t1 values (*); //等待,测试发现即使等待时间超过“wait_timeout”的阈值,也不会报错
select * from t1; //阻塞,如果超过wait_timeout设置的时间,报错:ERROR: 3797: Error while waiting for group transactions commit on group_replication_consistency= 'BEFORE'
unlock tables; //执行成功。此时Primary 上的insert 执行成功
select * from t1; //执行成功,返回结果为插入后的状态

 实现逻辑如下:

AFTER

1、在AFTER级别下,主库事务T1前面的操作一致,只是主库在完成prepare之后,引擎层提交前等待;

2、其他节点收到事务T1,然后放入relaylog顺序执行,直到prepare完成,提交之前,发送ack给所以其他成员;

3、当所有成员收到了所有ack后(M1默认已经发送过ack,因为之前就prepare完成了),才真正引擎层提交,从而其他事务可见。

4、在M3上T2事务开始时,T1正在提交,所以T2事务需要等待,直到事务T1提交完成,后续步骤与一般提交一致

  • BEFORE_AND_AFTER

 这个级别就是包含了BEFOR与AFTER。事务开始之前需等待先前事务提交,事务提交之前需等待所有节点都完成。整个实现逻辑如下:

BEFORE_AND_AFTER

2.3 BEFOR VS AFTER

 我们知道,AFTER与BEFOR两级别下,事务都需要等待,但其中有很大的不同,这里进行对比说明。

  • 等待时机点不同

 BEFOR级别下,是在事务执行前进行等待,对RW事务和RO事务都有影响;AFTER级别下,实在事务执行之后,引擎提交之前进行等待,仅仅对RW事务有影响。

  • 等待范围不同

 先看如下实验:

会话1(主,默认EVENTUAL) 会话2(从,默认EVENTUAL)
set @@session.group_replication_consistency ='AFTER' ;
lock table t1 read;
insert into t1 values (*); //等待
select * from t2; //执行成功
set @@session.group_replication_consistency ='BEFOR' ;
select * from t2; //阻塞
unlock tables; //执行成功。此时Primary 上的insert 执行成功

 从上述实验可以看出:一旦事务在应用队列中,在BEFOR隔离级别下,需等待之前事务都提交,不管是否是操作同一张表或者记录。

  • 影响范围不同

 BEFOR级别下,影响的仅仅是本成员中事务本身,也就是他并不需要与其他成员协调,仅仅影响本事务

 AFTER级别下,需要其他事务等待该事物的提交,该影响是整个组复制集群。

  • 查看等待信息

查看BEFOR级别下,造成事务等待,可以通过如下命令查看:

SELECT * FROM information_schema.processlist 
WHERE state='Executing hook on transaction begin.';

查看AFTER级别下,找出事务等待,可以通过如下命令查看:

SELECT * FROM information_schema.processlist 
WHERE state='waiting for handler commit';

更多信息可以

SELECT *, TIME_TO_SEC(TIMEDIFF(now(),trx_started)) lock_time_sec 
FROM information_schema.innodb_trx JOIN information_schema.processlist
ON processlist.ID=innodb_trx.trx_mysql_thread_id 
WHERE state='waiting for handler commit' ORDER BY trx_started\G

三、总结

 更为准确的准确对数据一致性进行控制固然很好,但是也存在一定风险。比如如果设置了全局AFTER级别,因为“AFTER”很有可能导致,当Primary上有大量的变更、DDL、备份等操作时,commit被延迟,并且延迟可能会导致独有的业务全部需要连接到Primary。

你可能感兴趣的:(有关MySQL组复制的事务一致性参数理解)