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

继上三期月报:
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写入的顺序完全和主库一致,但可以缓解这种情况。

时间: 2024-11-10 00:30:39

MySQL · 特性分析 ·MySQL 5.7新特性系列四的相关文章

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

继上两期月报,MySQL5.7新特性之一介绍了一些新特性及兼容性问题,MySQL 5.7新特性之二介绍了临时表的优化和实现. 这期我们一起来学习下undo空间管理,重点介绍truncate功能. 1. 背景 InnoDB存储引擎中,undo在完成事务回滚和MVCC之后,就可以purge掉了,但undo在事务执行过程中,进行的空间分配如何回收,就变成了一个问题. 我们亲历用户的小实例,因为一个大事务,导致ibdata file到800G大小. 我们先大致看下InnoDB的undo在不同的版本上的一

重新想象 Windows 8.1 Store Apps (91) - 后台任务的新特性: 下载和上传的新特性, 程序启动前预下载网络资源, 后台任务的其它新特性

原文:重新想象 Windows 8.1 Store Apps (91) - 后台任务的新特性: 下载和上传的新特性, 程序启动前预下载网络资源, 后台任务的其它新特性 [源码下载] 重新想象 Windows 8.1 Store Apps (91) - 后台任务的新特性: 下载和上传的新特性, 程序启动前预下载网络资源, 后台任务的其它新特性 作者:webabcd 介绍重新想象 Windows 8.1 Store Apps 之后台任务的新特性 下载和上传的新特性 程序启动前预下载网络资源 后台任务

MySQL · 特性分析 · MySQL 5.7 外部XA Replication实现及缺陷分析

MySQL 5.7 外部XA Replication实现及缺陷分析 MySQL 5.7增强了分布式事务的支持,解决了之前客户端退出或者服务器关闭后prepared的事务回滚和服务器宕机后binlog丢失的情况. 为了解决之前的问题,MySQL5.7将外部XA在binlog中的记录分成了两部分,使用两个GTID来记录.执行prepare的时候就记录一次binlog,执行commit/rollback再记录一次.由于XA是分成两部分记录,那么XA事务在binlog中就可能是交叉出现的.Slave端的

IE10对CSS3新特性的支持和HTML5新特性的支持

文章简介:IE10将增强对HTML5和CSS3的支持. Windows 8 终于发布了,虽然现在可用的只是开发者预览版,好消息是,IE 10 也随着发了,虽然现在还只有Windows 8可用.我们来看下IE10都有哪些新特性吧. IE开发者中心给到了一份详细的针对前端开发者的文档,列出了IE10支持的HTML5和CSS3新特性.嗯,它终于跟上了: CSS3 css region css3多列 Flexbox grid 定位浮动(positioned float) 3D变换(3D transfro

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

1. 背景 MySQL 5.7在2015-10-21发布了GA版本,即5.7.9,目前小版本已经到了5.7.12.5.7新增了许多新的feature和优化,接下来一个系列,我们就一起来尝尝鲜.首先这次主要是预览feature的变化以及兼容性问题.后面的系列,会针对重要的feature展开来学习. 2 安全相关的特性 2.1 认证插件 mysql.user表中的plugin更改成not null,5.7开始不再支持mysql_old_password的认证插件,推荐全部使用mysql_native

MySQL 5.6的72个新特性(译)

一,安全提高 1. 提供保存加密认证信息的方法,使用.mylogin.cnf文件.使用mysql_config_editor可以创建此文件.这个文件可以进行连接数据库的访问授权.mysql_config_editor会进行加密而不是明文存储.客户端只会在内存中进行解密.这样密码以非明文方式存储,也不会在命令行或者环境变量中暴露.得到更多信息,访问Section 4.6.6, "mysql_config_editor - MySQL Configuration Utility". 2.

MySQL · 特性分析 · innodb buffer pool相关特性

背景 innodb buffer pool做为innodb最重要的缓存,其缓存命中率的高低会直接影响数据库的性能.因此在数据库发生变更,比如重启.主备切换实例迁移等等,innodb buffer poll 需要一段时间预热,期间数据库的性能会受到明显影响. 另外mysql 5.7以前innodb buffer pool缓存大小修改不是动态的,重启才能生效.因此innodb buffer pool的预热和innodb buffer pool大小的动态修改,对性能要求较高的应用来说是不错的特性,下面

【MySQL】MySQL5.6新特性之Index Condition Pushdown

一 概念介绍    Index Condition Pushdown (ICP)是MySQL 5.6 版本中的新特性,是一种在存储引擎层使用索引过滤数据的一种优化方式.a 当关闭ICP时,index 仅仅是data access 的一种访问方式,存储引擎通过索引回表获取的数据会传递到MySQL Server 层进行where条件过滤.b 当打开ICP时,如果部分where条件能使用索引中的字段,MySQL Server 会把这部分下推到引擎层,可以利用index过滤的where条件在存储引擎层进

MySQL5.0新特性教程 存储过程:第三讲

The New SQL Statements 新SQL语句 Variables 变量 在复合语句中声明变量的指令是DECLARE. (1) Example with two DECLARE statements 两个DECLARE语句的例子 WHILE ... END WHILE CREATE PROCEDURE p8 () BEGIN DECLARE a INT; DECLARE b INT; SET a = 5; SET b = 5; INSERT INTO t VALUES (a); SE