返回顶部
首页 > 资讯 > 数据库 >MySQL中基于WRITESET的并行复制方式是什么
  • 827
分享到

MySQL中基于WRITESET的并行复制方式是什么

2024-04-02 19:04:59 827人浏览 八月长安
摘要

本篇内容主要讲解“Mysql中基于WRITESET的并行复制方式是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“mysql中基于WRITESET的并行复制方

本篇内容主要讲解“Mysql中基于WRITESET的并行复制方式是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习mysql中基于WRITESET的并行复制方式是什么”吧!

一、奇怪的last commit

我们先来看一个截图,仔细观察其中的last commit:
MySQL中基于WRITESET的并行复制方式是什么

我们可以看到其中的last commit看起来是乱序的,这种情况在基于COMMIT_ORDER 的并行复制方式下是不可能出现的。实际上它就是我们前面说的基于WRITESET的并行复制再尽可能降低的last commit的结果。这种情况会在MTS从库获得更好的并行回放效果,第19节将会详细解释并行判定的标准。

二、Writeset是什么

实际上Writeset是一个集合,使用的是c++ STL中的set容器,在类Rpl_transaction_write_set_ctx中包含了如下定义:

std::set<uint64> write_set_unique;

集合中的每一个元素都是hash值,这个hash值和我们的transaction_write_set_extraction参数指定的算法有关,其来源就是行数据的主键和唯一键。每行数据包含了两种格式:

  • 字段值为二进制格式

  • 字段值为字符串格式

每行数据的具体格式为:

主键/唯一键名称分隔符库名分隔符库名长度表名分隔符表名长度键字段1分隔符长度键字段2分隔符长度其他字段…















在Innodb层修改一行数据之后会将这上面的格式的数据进行hash后写入到Writeset中。可以参考函数add_pke,后面我也会以伪代码的方式给出部分流程。

但是需要注意一个事务的所有的行数据的hash值都要写入到一个Writeset。如果修改的行比较多那么可能需要更多内存来存储这些hash值。虽然8字节比较小,但是如果一个事务修改的行很多,那么还是需要消耗较多的内存资源的。

为了更直观的观察到这种数据格式,可以使用debug的方式获取。下面我们来看一下。

三、Writeset的生成

我们使用如下表:

mysql> use test
Database changed
mysql> show create table jj10 \G
*************************** 1. row ***************************
       Table: jj10
Create Table: CREATE TABLE `jj10` (
  `id1` int(11) DEFAULT NULL,
  `id2` int(11) DEFAULT NULL,
  `id3` int(11) NOT NULL,
  PRIMARY KEY (`id3`),
  UNIQUE KEY `id1` (`id1`),
  KEY `id2` (`id2`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

我们写入一行数据:

insert into jj10 values(36,36,36);

这一行数据一共会生成4个元素分别为:

注意:这里显示的?是分隔符

1. 主键二进制格式
(gdb) p pke
$1 = "PRIMARY?test?4jj10?4\200\000\000$?4"
**注意:\200\000\000$ :为3个八进制字节和ASCII字符 $,
其转换为16进制就是“0X80 00 00 24 ”**

分解为:

主键名称分隔符库名分隔符库名长度表名分隔符表名长度主键字段1分隔符长度
PRIMARY?test?4jj10?40x80 00 00 24?4
2. 主键字符串格式:
(gdb) p pke
$2 = "PRIMARY?test?4jj10?436?2"

分解为:
| 主键名称 | 分隔符 | 库名 | 分隔符 |库名长度 | 表名 |分隔符|表名长度|主键字段1|分隔符|长度 |
| ——— | ——— | ——— | ——— | ——— | ——— | ——— | ——— | ——— |——— |———|
| PRIMARY |?| test|?|4|jj10|?|4|36 | ?|2|

3. 唯一键二进制格式
(gdb) p pke
$3 = "id1?test?4jj10?4\200\000\000$?4"

解析同上

4. 唯一键字符串格式:
(gdb) p pke
$4 = "id1?test?4jj10?436?2"

解析同上

最终这些数据会通过hash算法后写入到Writeset中。

四、函数add_pke的大概流程

下面是一段伪代码,用来描述这种生成过程:

如果表中存在索引:
   将数据库名,表名信息写入临时变量   
   循环扫描表中每个索引:
        如果不是唯一索引:
             退出本次循环继续循环。
        循环两种生成数据的方式(二进制格式和字符串格式):
             将索引名字写入到pke中。
             将临时变量信息写入到pke中。
             循环扫描索引中的每一个字段:
                将每一个字段的信息写入到pke中。
                如果字段扫描完成:
                   将pke生成hash值并且写入到写集合中。
    如果没有找到主键或者唯一键记录一个标记,后面通过这个标记来
    判定是否使用Writeset的并行复制方式

五、Writeset设置对last commit的处理方式

前一节我们讨论了基于ORDER_COMMIT的并行复制是如何生成last_commit和seq number的。实际上基于WRITESET的并行复制方式只是在ORDER_COMMIT的基础上对last_commit做更进一步处理,并不影响原有的ORDER_COMMIT逻辑,因此如果要回退到ORDER_COMMIT逻辑非常方便。可以参考MYSQL_BIN_LOG::write_gtid函数。

根据binlog_transaction_dependency_tracking取值的不同会做进一步的处理,如下:

  • ORDER_COMMIT:调用m_commit_order.get_dependency函数。这是前面我们讨论的方式。

  • WRITESET:调用m_commit_order.get_dependency函数,然后调用m_writeset.get_dependency。可以看到m_writeset.get_dependency函数会对原有的last commit做处理。

  • WRITESET_SESSION:调用m_commit_order.get_dependency函数,然后调用m_writeset.get_dependency再调用m_writeset_session.get_dependency。m_writeset_session.get_dependency会对last commit再次做处理。

这段描述的代码对应:

    case DEPENDENCY_TRACKING_COMMIT_ORDER:
      m_commit_order.get_dependency(thd, sequence_number, commit_parent);
      break;
    case DEPENDENCY_TRACKING_WRITESET:
      m_commit_order.get_dependency(thd, sequence_number, commit_parent);
      m_writeset.get_dependency(thd, sequence_number, commit_parent);
      break;
    case DEPENDENCY_TRACKING_WRITESET_SESSION:
      m_commit_order.get_dependency(thd, sequence_number, commit_parent);
      m_writeset.get_dependency(thd, sequence_number, commit_parent);
      m_writeset_session.get_dependency(thd, sequence_number, commit_parent);
      break;

六、Writeset的历史MAP

我们到这里已经讨论了Writeset是什么,也已经说过如果要降低last commit的值我们需要通过对事务的Writeset和Writeset的历史MAP进行比对,看是否冲突才能决定降低为什么值。那么必须在内存中保存一份这样的一个历史MAP才行。在源码中使用如下方式定义:

  
  typedef std::map<uint64,int64> Writeset_history; //map实现
  Writeset_history m_writeset_history;

我们可以看到这是C++ STL中的map容器,它包含两个元素:

  • Writeset的hash值

  • 最新一次本行数据修改事务的seq number

它是按照Writeset的hash值进行排序的。

其次内存中还维护一个叫做m_writeset_history_start的值,用于记录Writeset的历史MAP中最早事务的seq number。如果Writeset的历史MAP满了就会清理这个历史MAP然后将本事务的seq number写入m_writeset_history_start,作为最早的seq number。后面会看到对于事务last commit的值的修改总是从这个值开始然后进行比较判断修改的,如果在Writeset的历史MAP中没有找到冲突那么直接设置last commit为这个m_writeset_history_start值即可。下面是清理Writeset历史MAP的代码:

  if (exceeds_capacity || !can_use_writesets)
//Writeset的历史MAP已满
  {
    m_writeset_history_start= sequence_number; 
//如果超过最大设置,清空writeset history。从当前seq number 重新记录, 也就是最小的那个事务seq number
    m_writeset_history.clear();
//清空历史MAP
  }

七、Writeset的并行复制对last commit的处理流程

这里介绍一下整个处理的过程,假设如下:

  • 当前通过基于ORDER_COMMIT的并行复制方式后,构造出来的是(last commit=125,seq number=130)。

  • 本事务修改了4条数据,我分别使用ROW1/ROW7/ROW6/ROW10代表。

  • 表只包含主键没有唯一键,并且我的图中只保留行数据的二进制格式的hash值,而没有包含数据的字符串格式的hash值。

初始化情况如下图(图16-1,高清原图包含在文末原图中):
MySQL中基于WRITESET的并行复制方式是什么

  1. 第一步 设置last commit为writeset_history_start的值也就是100。

  2. 第二步 ROW1.HASHVAL在Writeset历史MAP中查找,找到冲突的行ROW1.HASHVAL将历史MAP中这行数据的seq number更改为130。同时设置last commit为120。

  3. 第三步 ROW7.HASHVAL在Writeset历史MAP中查找,找到冲突的行ROW7.HASHVAL将Writeset历史MAP中这行数据的seq number更改为130。由于历史MAP中对应的seq number为114,小于120不做更改。last commit依旧为120。

  4. 第四步 ROW6.HASHVAL在Writeset历史MAP中查找,找到冲突的行ROW6.HASHVAL将Writeset历史MAP中这行数据的seq number更改为130。由于历史MAP中对应的seq number为105,小于120不做更改。last commit依旧为120。

  5. 第五步 ROW10.HASHVAL在Writeset历史MAP中查找,没有找到冲突的行,因此需要将这一行插入到Writeset历史MAP中查找(需要判断是否导致历史MAP占满,如果占满则不需要插入,后面随即要清理掉)。即要将ROW10.HASHVAL和seq number=130插入到Writeset历史MAP中。

整个过程结束。last commit由以前的130降低为120,目的达到了。实际上我们可以看出Writeset历史MAP就相当于保存了一段时间以来修改行的快照,如果保证本次事务修改的数据在这段时间内没有冲突,那么显然是可以在从库并行执行的。last commit降低后如下图(图16-2,高清原图包含在文末原图中):
MySQL中基于WRITESET的并行复制方式是什么

整个逻辑就在函数Writeset_trx_dependency_tracker::get_dependency中,下面是一些关键代码,代码稍多:

if (can_use_writesets) //如果能够使用writeset 方式
  {
    
    exceeds_capacity=
      m_writeset_history.size() + writeset->size() > m_opt_max_history_size; 
//如果大于参数binlog_transaction_dependency_history_size设置清理标记
    
    int64 last_parent= m_writeset_history_start;
//临时变量,首先设置为最小的一个seq number
    for (std::set<uint64>::iterator it= writeset->begin(); it != writeset->end(); ++it)
//循环每一个Writeset中的每一个元素 
    {
      Writeset_history::iterator hst= m_writeset_history.find(*it);
//是否在writeset history中 已经存在了。 map中的元素是 key是writeset 值是sequence number
      if (hst != m_writeset_history.end()) //如果存在
      {    
        if (hst->second > last_parent && hst->second < sequence_number) 
          last_parent= hst->second;
//如果已经大于了不需要设置
        hst->second= sequence_number; 
//更改这行记录的sequence_number
      }
      else
      {
        if (!exceeds_capacity)
          m_writeset_history.insert(std::pair<uint64, int64>(*it, sequence_number));
//没有冲突则插入。
      }
    }
......
    if (!write_set_ctx->get_has_missing_keys())
//如果没有主键和唯一键那么不更改last commit
    {
      ;
      commit_parent= std::min(last_parent, commit_parent);
//这里对last commit做更改了。降低他的last commit
    }
  }
    }
  }
  if (exceeds_capacity || !can_use_writesets)
  {
    m_writeset_history_start= sequence_number; 
//如果超过最大设置 清空writeset history。从当前sequence 重新记录 也就是最小的那个事务seqnuce number
    m_writeset_history.clear();//清空真个MAP
  }

八、WRITESET_SESSION的方式

前面说过这种方式就是在WRITESET的基础上继续处理,实际上它的含义就是同一个session的事务不允许在从库并行回放。代码很简单,如下:

  int64 session_parent= thd->rpl_thd_ctx.dependency_tracker_ctx().
                        get_last_session_sequence_number();
//取本session的上一次事务的seq number
  if (session_parent != 0 && session_parent < sequence_number) 
//如果本session已经做过事务并且本次当前的seq number大于上一次的seq number
    commit_parent= std::max(commit_parent, session_parent);
//说明这个session做过多次事务不允许并发,修改为order_commit生成的last commit
  thd->rpl_thd_ctx.dependency_tracker_ctx().
    set_last_session_sequence_number(sequence_number);
//设置session_parent的值为本次seq number的值

经过这个操作后,我们发现这种情况最后last commit恢复成了ORDER_COMMIT的方式。

九、关于binlog_transaction_dependency_history_size参数说明

本参数默认值为25000。代表的是我们说的Writeset历史MAP中元素的个数。如前面分析的Writeset生成过程中修改一行数据可能会生成多个HASH值,因此这个值还不能完全等待于修改的行数,可以理解为如下:

  • binlog_transaction_dependency_history_size/2=修改的行数 * (1+唯一键个数)

我们通过前面的分析可以发现如果这个值越大那么在Writeset历史MAP中能容下的元素也就越多,生成的last commit就可能更加精确(更加小),从库并发的效率也就可能越高。但是我们需要注意设置越大相应的内存需求也就越高了。

十、没有主键的情况

实际上在函数add_pke中就会判断是否有主键或者唯一键,如果存在唯一键也是可以。Writeset中存储了唯一键的行数据hash值。参考函数add_pke,下面是判断:

      if (!((table->key_info[key_number].flags & (HA_NOSAME )) == HA_NOSAME)) 
//跳过非唯一的KEY
        continue;

如果没有主键或者唯一键那么下面语句将被触发:

  if (writeset_hashes_added == 0)
    ws_ctx->set_has_missing_keys();

然后我们在生成last commit会判断这个设置如下:

    if (!write_set_ctx->get_has_missing_keys())
//如果没有主键和唯一键那么不更改last commit
    {
      ;
      commit_parent= std::min(last_parent, commit_parent);//这里对last commit做更改了。降低他的last commit
    }
  }

因此没有主键可以使用唯一键,如果都没有的话WRITESET设置就不会生效回退到老的ORDER_COMMIT方式。

十一、为什么同一个session执行的事务也能生成同样的last commit

有了前面的基础,我们就很容易解释这种现象了。其主要原因就是Writeset的历史MAP的存在,只要这些事务修改的行没有冲突,也就是主键/唯一键不相同,那么在基于WRITESET的并行复制方式中就可以存在这种现象,但是如果binlog_transaction_dependency_tracking设置为WRITESET_SESSION则不会出现这种现象。

到此,相信大家对“MySQL中基于WRITESET的并行复制方式是什么”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

您可能感兴趣的文档:

--结束END--

本文标题: MySQL中基于WRITESET的并行复制方式是什么

本文链接: https://lsjlt.com/news/63438.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • MySQL中基于WRITESET的并行复制方式是什么
    本篇内容主要讲解“MySQL中基于WRITESET的并行复制方式是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“MySQL中基于WRITESET的并行复制方...
    99+
    2024-04-02
  • 浅析MySQL的WriteSet并行复制
    【历史背景】   岁月更迭中我已经从事MySQL-DBA这个工作三个年头,见证MySQL从“基本可用”,“边缘系统可以用MySQL”,“哦操!你怎么不用MySQL”;   正所谓!“一个数据库的境遇既取决于历史的进程...
    99+
    2022-05-31
    MySQL WriteSet并行复制 MySQL WriteSet MySQL 并行复制
  • Mysql中基于GTID的复制模式是什么
    这篇文章主要介绍了Mysql中基于GTID的复制模式是什么,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。GTID定义GTID( Global...
    99+
    2024-04-02
  • mysql基于日志的主从复制是什么
    这篇文章主要介绍了mysql基于日志的主从复制是什么,具有一定借鉴价值,需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获。下面让小编带着大家一起了解一下。       ...
    99+
    2024-04-02
  • Mysql复制方式(半同步复制,并行复制,多源复制
    一、MySQL主从复制(异步复制,默认) Mysql主从复制原理 Mysql的复制原理大致如下: 1.主库记录binlog日志 在每次准备提交事务完成数据更新前,主库将数据更新的事件记录到二进制日志binlog中。主库上的s...
    99+
    2017-10-08
    Mysql复制方式(半同步复制,并行复制,多源复制
  • mysql 基于组提交的并发复制小结
    一:MySQL 5.7并行复制初理解...
    99+
    2024-04-02
  • MySQL基于GTID复制的设置方法
    下文给大家带来有关MySQL基于GTID复制的设置方法内容,相信大家一定看过类似的文章。我们给大家带来的有何不同呢?一起来看看正文部分吧,相信看完MySQL基于GTID复制的设置方法你一定会有所收获。GTI...
    99+
    2024-04-02
  • MySQL Replication中并行复制怎么实现
    这篇文章主要介绍“MySQL Replication中并行复制怎么实现”,在日常操作中,相信很多人在MySQL Replication中并行复制怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对...
    99+
    2023-07-02
  • mysql表复制的方法是什么
    在MySQL中,可以使用以下方法来复制表: 使用CREATE TABLE … SELECT 语句复制表的结构和数据: C...
    99+
    2024-05-21
    mysql
  • MySQL中复制机制的原理是什么
    MySQL中复制机制的原理是什么,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。背景介绍复制,就是对数据的完整拷贝,说到为什么要...
    99+
    2024-04-02
  • MySQL Replication中的并行复制示例详解
    目录传统单线程复制说明总结mysql5.6基于库级别的并行复制MySQL5.7基于组提交的并行复制组提交说明MySQL8.0基于writeset的并行复制关键参数查看参数配置项说明引用资料:传统单线程复制说明 众所周知,...
    99+
    2022-07-01
    MySQL Replication并行复制 MySQL 并行复制
  • mysql中并发控制的原理是什么
    mysql中并发控制的原理是什么,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。1、mysql的逻辑框架mysql逻辑框架图如下:最上层是处...
    99+
    2024-04-02
  • mysql 复制的3种模式分别是什么
    今天就跟大家聊聊有关mysql 复制的3种模式分别是什么,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。 -- 基于SQL...
    99+
    2024-04-02
  • mysql复制数据库的方法是什么
    MySQL复制数据库的方法有多种,以下是其中一种常用的方法: 使用mysqldump命令备份源数据库: mysqldump -u...
    99+
    2024-04-09
    mysql 数据库
  • Mysql的链条式复制的优缺点是什么
    这篇文章主要介绍“Mysql的链条式复制的优缺点是什么”,在日常操作中,相信很多人在Mysql的链条式复制的优缺点是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Mysq...
    99+
    2024-04-02
  • 基于python中mysql复制工具的示例分析
    这篇文章主要为大家展示了“基于python中mysql复制工具的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“基于python中mysql复制工具的示例...
    99+
    2024-04-02
  • MySql中主从复制机制的原理是什么
    今天就跟大家聊聊有关MySql中主从复制机制的原理是什么,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。主从复制机制MySQL基于binlog实现主从复制,从节点跟踪并获取主节点bin...
    99+
    2023-06-14
  • python复制文件并重命名的方法是什么
    在Python中,可以使用shutil模块的copy2函数来复制文件并重命名。下面是一个示例代码:pythonimport shut...
    99+
    2023-10-18
    python
  • PHP中基于控制器的路由实现方式
    随着Web应用程序的复杂性增加,有效管理URL和路由成为了开发过程中的一个重要任务。在PHP中,可以使用基于控制器的路由实现方式来解决这个问题。本文将介绍基于控制器的路由实现方式,并提供具体的代码示例。基本原理基于控制器的路由实现方式是指将...
    99+
    2023-10-21
    PHP 控制器路由 基于控制器的路由实现 PHP 控制器路由方式
  • mysql复制的原理是什么
    今天就跟大家聊聊有关mysql复制的原理是什么,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。1 复制概述   &nbs...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作