MySQL · 特性分析 ·MySQL 5.7新特性系列四

本文涉及的产品
云原生数据库 PolarDB 分布式版,标准版 2核8GB
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS PostgreSQL Serverless,0.5-4RCU 50GB 3个月
推荐场景:
对影评进行热评分析
简介: 继上三期月报:MySQL 5.7新特性之一介绍了一些新特性及兼容性问题MySQL 5.7新特性之二介绍了临时表的优化和实现MySQL 5.7新特性之三介绍了undo表空间的truncate功能这期我们一起来学习下MySQL 5.7的并行复制。1. 背景MySQL的master<->slave的部署结构,使用binlog日志保持数据的同步,全局有序的binlog在备库按照提交顺

继上三期月报:
MySQL 5.7新特性之一介绍了一些新特性及兼容性问题
MySQL 5.7新特性之二介绍了临时表的优化和实现
MySQL 5.7新特性之三介绍了undo表空间的truncate功能

这期我们一起来学习下MySQL 5.7的并行复制。

1. 背景

MySQL的master<->slave的部署结构,使用binlog日志保持数据的同步,全局有序的binlog在备库按照提交顺序进行回放。
由于新硬件的发展,SSD的引入和多core的CPU,master节点的并发处理能力持续提升,slave节点完全按照binlog写入顺序的单线程回放,已完全跟不上master节点的吞吐能力,导致HA切换和数据保护带来巨大的挑战。

2. 并行复制的演进

从MySQL5.5版本以后,开始引入并行复制的机制,是MySQL的一个非常重要的特性。

MySQL5.6开始支持以schema为维度的并行复制,即如果binlog row event操作的是不同的schema的对象,在确定没有DDL和foreign key依赖的情况下,就可以实现并行复制。

社区也有引入以表为维度或者以记录为维度的并行复制的版本,不管是schema,table或者record,都是建立在备库slave实时解析row格式的event进行判断,保证没有冲突的情况下,进行分发来实现并行。

MySQL5.7的并行复制,multi-threaded slave即MTS,期望最大化还原主库的并行度,实现方式是在binlog event中增加必要的信息,以便slave节点根据这些信息实现并行复制。

下面我们就来看下MySQL 5.7的实现方式:

3. MySQL 5.7 并行复制

MySQL 5.7的并行复制建立在group commit的基础上,所有在主库上能够完成prepared的语句表示没有数据冲突,就可以在slave节点并行复制。
我们先来回顾一下group commit的情况:

1. group commit的过程:
    1. binlog prepare
    2. InnoDB prepare
    3. binlog commit(ordered commit)
        --3.1 Stage #1: flushing transactions to binary log
        --3.2 Stage #2: Syncing binary log file to disk
        --3.3 Stage #3: Commit all transactions in order.
    4. InnoDB commit

在ordered commit的过程中:
1. 由leader线程帮助FLUSH队列中的线程完成flush binlog操作,
2. 由leader线程帮助SYNC队列中的线程完成sync binlog操作,

为了表示主库并行度,在binlog row event增加了如下的标识:

#160807 15:48:10 server id 100  end_log_pos 739 CRC32 0x2237b2ef        GTID    last_committed=0        sequence_number=3
SET @@SESSION.GTID_NEXT= '8108dc48-47de-11e6-8690-a0d3c1f20ae4:3'/*!*/;

即在gtid_event中增加两个字段:

class Gtid_event: public Binary_log_event
{
public:
  /*
    The transaction's logical timestamps used for MTS: see
    Transaction_ctx::last_committed and
    Transaction_ctx::sequence_number for details.
    Note: Transaction_ctx is in the MySQL server code.
  */
  long long int last_committed;
  long long int sequence_number;
  /**
    Ctor of Gtid_event

    The layout of the buffer is as follows
    +-------------+-------------+------------+---------+----------------+
    | commit flag | ENCODED SID | ENCODED GNO| TS_TYPE | logical ts(:s) |
    +-------------+-------------+------------+---------+----------------+
    TS_TYPE is from {G_COMMIT_TS2} singleton set of values

代码中为每一个transaction准备了如下的字段:

class Transaction_ctx
{
    ......
    int64 last_committed;
    int64 sequence_number;
}

MYSQL_BIN_LOG全局对象中维护了两个结构:

class MYSQL_BIN_LOG: public TC_LOG
{
  ......
  /* Committed transactions timestamp */
   Logical_clock max_committed_transaction;
  /* "Prepared" transactions timestamp */
   Logical_clock transaction_counter;
}

事务中的sequence_number是一个全局有序递增的数字,每个事务递增1,来源mysql_bin_log.tranaction_counter.
和gtid一对一的关系,即在flush阶段,和gtid生成的时机一致,代码参考:

binlog_cache_data::flush
{   
     if (flags.finalized) {
       Transaction_ctx *trn_ctx= thd->get_transaction();
       trn_ctx->sequence_number= mysql_bin_log.transaction_counter.step();
     }
     .......
     mysql_bin_log.write_gtid(thd, this, &writer)))
     mysql_bin_log.write_cache(thd, this, &writer);
}

事务中last_committed表示在这个commit下的事务,都是可以并行的,即没有冲突,
Transaction_ctx中的last_committed在每个语句prepared的时候进行初始化,来源mysql_bin_log.max_committed_transaction

static int binlog_prepare(handlerton *hton, THD *thd, bool all)
{
    ......
    Logical_clock& clock= mysql_bin_log.max_committed_transaction;
    thd->get_transaction()->
      store_commit_parent(clock.get_timestamp());
}

而mysql_bin_log.max_committed_transaction的更新是在group commit提交的时候进行变更。

MYSQL_BIN_LOG::process_commit_stage_queue(THD *thd, THD *first)
{
    ......
    for (THD *head= first ; head ; head = head->next_to_commit)
    {
        if (thd->get_transaction()->sequence_number != SEQ_UNINIT)
            update_max_committed(head);
    }
}

即获取这个group commit队列中的最大的sequence_number当成当前的max_committed_transaction。

所以,这个机制可以理解成,在group commit完成之前,所有可以成功prepared的语句,没有事实上的冲突,
分配成相同的last_committed,就可以在slave节点并行复制。

例如下面时序的事务:

session 1:insert into t1 value(100, 'xpchild');
session 2:insert into t1 value(101, 'xpchild');
session 2:commit
session 1:commit

Binlog日志片段如下:

# at 1398
#160807 15:38:14 server id 100  end_log_pos 1463 CRC32 0xd6141f71       GTID    last_committed=5        sequence_number=6
SET @@SESSION.GTID_NEXT= '8108dc48-47de-11e6-8690-a0d3c1f20ae4:6'/*!*/;
'/*!*/;
### INSERT INTO `tp`.`t1`
### SET
###   @1=101 /* INT meta=0 nullable=0 is_null=0 */
###   @2='xpchild' /* VARSTRING(100) meta=100 nullable=1 is_null=0 */

COMMIT/*!*/;
# at 1658
#160807 15:38:24 server id 100  end_log_pos 1723 CRC32 0xa24923a8       GTID    last_committed=5        sequence_number=7
SET @@SESSION.GTID_NEXT= '8108dc48-47de-11e6-8690-a0d3c1f20ae4:7'/*!*/;
### INSERT INTO `tp`.`t1`
### SET
###   @1=100 /* INT meta=0 nullable=0 is_null=0 */
###   @2='xpchild' /* VARSTRING(100) meta=100 nullable=1 is_null=0 */

两个insert语句在prepared的时候,没有事实上的冲突,都获取当前最大的committed number = 5.
提交的过程中,保持sequence_number生成时候的全局有序性,备库恢复的时候,这两个事务就可以并行完成。

但又如下面的case:

session 1: insert into t1 value(100, 'xpchild');

session 2: insert into t1 value(101, 'xpchild');
session 2: commit;

session 3: insert into t1 value(102, 'xpchild');
session 3: commit;

session 1: commit;

产生如下的顺序:

#160807 15:47:58 server id 100  end_log_pos 219 CRC32 0x3f295e2b        GTID    last_committed=0        sequence_number=1
### INSERT INTO `tp`.`t1`
### SET
###   @1=101 /* INT meta=0 nullable=0 is_null=0 */
.....
#160807 15:48:05 server id 100  end_log_pos 479 CRC32 0xda52bed0        GTID    last_committed=1        sequence_number=2
### INSERT INTO `tp`.`t1`
### SET
###   @1=102 /* INT meta=0 nullable=0 is_null=0 */
......
#160807 15:48:10 server id 100  end_log_pos 739 CRC32 0x2237b2ef        GTID    last_committed=0        sequence_number=3
### INSERT INTO `tp`.`t1`
### SET
###   @1=100 /* INT meta=0 nullable=0 is_null=0 */
....

session 1和session 2语句执行不冲突,分配了相同的last_committed,
session 2提交,推高了last_committed,所以session 3的laste_committed变成了1,
最后session 1提交。

注意: 这就是MySQL 5.7.3之后的改进:

在MySQL 5.7.3之前,必须在一个group commit之内的事务,才能够在slave节点并行复制,但如上面的这个case。

session 1 和session 2虽然commit的时间有差,并且不在一个group commit,生成的binlog也没有连续,但事实上是可以并行恢复执行的。

所以从MySQL 5.7.3之后,并行恢复,减少了group commit的依赖,但group commit仍然对并行恢复起着非常大的作用。

4. MySQL 5.7 并行复制参数

MySQL 5.7增加了如下参数:

mysql> show global variables like '%slave_parallel_type%';
+---------------------+---------------+
| Variable_name       | Value         |
+---------------------+---------------+
| slave_parallel_type | LOGICAL_CLOCK |
+---------------------+---------------+
1 row in set (0.00 sec)

slave_parallel_type取值:
1. DATABASE: 默认值,兼容5.6以schema维度的并行复制
2. LOGICAL_CLOCK: MySQL 5.7基于组提交的并行复制机制

5. MySQL 5.7 并行复制影响因素

group commit delay

首先,并行复制必须建立在主库的真实负载是并行的基础上,才能使MTS有机会在slave节点上完成并行复制,
其次,MySQL 5.7前面讨论的实现机制,可以人工的增加group commit的delay,打包更多的事务在一起,提升slave复制的并行度。但从5.7.3开始,已经减少了group commit的依赖,
尽量减少delay参数设置对主库的影响。

合理设置如下参数;

mysql> show global variables like '%group_commit%';
+-----------------------------------------+--------+
| Variable_name                           | Value  |
+-----------------------------------------+--------+
| binlog_group_commit_sync_delay          | 100000 |
| binlog_group_commit_sync_no_delay_count | 0      |
+-----------------------------------------+--------+

6. 一点建议

  1. 尽量使用row格式的binlog
  2. slave_parallel_workers 太多的线程会增加线程间同步的开销,建议4-8个slave线程,根据测试来最终确定。
  3. 如果客户端有并行度,不用刻意增加master的group commit,减少对主库的影响。

另外:
booking在使用的时候遇到的如下case:

数据库的部署结构是:master->slave1->slave2

假设,当t1,t2,t3,t4四个事务在master group commit中,那么slave1线程就可以并行执行这四个事务,
但在slave1执行的过程中,分成了两个group commit,那么在slave2节点上,并行度就降低了一倍。

booking给出的后续的解法,如果slave不多,建议都挂载在master上,如果slave过多,考虑使用binlog server,来避免这样的问题。

但其实在slave1节点上进行并行恢复的时候,保持着主库的last_committed和sequence_number不变,虽然无法保证binlog写入的顺序完全和主库一致,但可以缓解这种情况。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
12天前
|
存储 关系型数据库 MySQL
MySQL 8.0特性-自增变量的持久化
【11月更文挑战第8天】在 MySQL 8.0 之前,自增变量(`AUTO_INCREMENT`)的行为在服务器重启后可能会发生变化,导致意外结果。MySQL 8.0 引入了自增变量的持久化特性,将其信息存储在数据字典中,确保重启后的一致性。这提高了开发和管理的稳定性,减少了主键冲突和数据不一致的风险。默认情况下,MySQL 8.0 启用了这一特性,但在升级时需注意行为变化。
|
1月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1641 14
|
1月前
|
存储 关系型数据库 MySQL
基于案例分析 MySQL 权限认证中的具体优先原则
【10月更文挑战第26天】本文通过具体案例分析了MySQL权限认证中的优先原则,包括全局权限、数据库级别权限和表级别权限的设置与优先级。全局权限优先于数据库级别权限,后者又优先于表级别权限。在权限冲突时,更严格的权限将被优先执行,确保数据库的安全性与资源合理分配。
|
2月前
|
JSON 关系型数据库 MySQL
MySQL 8.0 新特性
MySQL 8.0 新特性
148 10
MySQL 8.0 新特性
|
2月前
|
存储 Oracle 关系型数据库
Oracle和MySQL有哪些区别?从基本特性、技术选型、字段类型、事务、语句等角度详细对比Oracle和MySQL
从基本特性、技术选型、字段类型、事务提交方式、SQL语句、分页方法等方面对比Oracle和MySQL的区别。
536 18
Oracle和MySQL有哪些区别?从基本特性、技术选型、字段类型、事务、语句等角度详细对比Oracle和MySQL
|
1月前
|
SQL 安全 关系型数据库
MySQL8.2有哪些新特性?
【10月更文挑战第3天】MySQL8.2有哪些新特性?
40 2
|
1月前
|
SQL 关系型数据库 MySQL
MySQL 更新1000万条数据和DDL执行时间分析
MySQL 更新1000万条数据和DDL执行时间分析
117 4
|
1月前
|
SQL 自然语言处理 关系型数据库
Vanna使用ollama分析本地MySQL数据库
这篇文章详细介绍了如何使用Vanna结合Ollama框架来分析本地MySQL数据库,实现自然语言查询功能,包括环境搭建和配置流程。
209 0
|
2月前
|
Oracle NoSQL 关系型数据库
主流数据库对比:MySQL、PostgreSQL、Oracle和Redis的优缺点分析
主流数据库对比:MySQL、PostgreSQL、Oracle和Redis的优缺点分析
434 2
|
2月前
|
存储 关系型数据库 MySQL
分析MySQL主从复制中AUTO_INCREMENT值不一致的问题
通过对 `AUTO_INCREMENT`不一致问题的深入分析和合理应对措施的实施,可以有效地维护MySQL主从复制环境中数据的一致性和完整性,确保数据库系统的稳定性和可靠性。
112 6

相关产品

  • 云数据库 RDS MySQL 版
  • 下一篇
    无影云桌面