MySQL:MGR 学习(1):写集合(Write set)

本文涉及的产品
RDS AI 助手,专业版
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
简介:

水平有限,有误请谅解。

源码版本5.7.22

一、什么是写集合(Write set)

实际上写集合定义在类Rpl_transaction_write_set_ctx中,其中主要包含两个数据结构

  • std::vector write_set;
  • std::set write_set_unique;

第一个是一个vecotr数组,第二个是一个set集合,它们中的每一元素都是一个hash值,其hash来源自函数add_pke,包含了:

  • 非唯一索引名称+分隔符+库名+分隔符+库名长度+表名+分隔符+表名长度+索引字段1数值+分隔符 +索引字段1长度 [+ 索引字2段数值+分隔符 +索引字段2长度 .....]

注意唯一索引也会计入到写集合中。
在MGR中主键是有着极其重要的地位,是判断是否冲突的重要依据,最后写集合信息会封装进Transaction_context_log_event,同其他binlog event信息一起发送给其他节点。同时函数add_pke在生成写集合成员原始数据的时候(hash之前的数据)对每行索引值还记录两种格式:

  • 按照MySQL字段格式的字段值和长度
  • 按照字符串格式记录的字段值和长度

而生成写集合的是在Innodb层完成更改操作,MySQL层写入binlog event之前。

二、写集合原始数据(hash前)的列子

如下表:

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 

主键 PRIMARY+分隔符+库名 test+分隔符+库名长度 4+表名 jj10+分隔符+表名长度 4+主键值 0X80 00 00 24 +分隔符+int字段类型长度 4

  • 写集合元素2:
(gdb) p pke
$2 = "PRIMARY½test½4jj10½436½2"

主键 PRIMARY+分隔符+库名 test+分隔符+库名长度 4+表名 jj10+分隔符+表名长度 4+主键值字符串显示 "36" +分隔符+字符串"36"长度为2

  • 写集合元素3:
(gdb) p pke
$3 = "id1½test½4jj10½4\200\000\000$½4"

同上只是这里不是主键是唯一键id1

  • 写集合元素4:
(gdb) p pke
$4 = "id1½test½4jj10½436½2"

同上只是这里不是主键是唯一键id1

三、函数add_pke解析

这里抛开了外键的逻辑主要逻辑如下:

如果表中存在索引:
   将数据库名,表名信息写入临时变量   
   循环扫描表中每个索引:
        如果不是唯一索引:
             退出本次循环继续循环。
        循环两种生成数据的方式(MySQL格式和字符串格式):
             将索引名字写入到pke中。
             将临时变量信息写入到pke中。
             循环扫描索引中的每一个字段:
                将每一个字段的信息写入到pke中。
                如果字段扫描完成:
                   将pke生成hash值并且写入到写集合中。

源码注释如下:

Rpl_transaction_write_set_ctx* ws_ctx=                     //THD  Transaction_ctx  m_transaction_write_set_ctx
    thd->get_transaction()->get_transaction_write_set_ctx(); //本内存空间在线程初始化的时候分配    m_transaction(new Transaction_ctx()), 
  int writeset_hashes_added= 0;

  if(table->key_info && (table->s->primary_key < MAX_KEY)) //typedef struct st_key  
  {
    char value_length_buffer[VALUE_LENGTH_BUFFER_SIZE];
    char* value_length= NULL;

    std::string pke_schema_table;
    pke_schema_table.reserve(NAME_LEN * 3);
    pke_schema_table.append(HASH_STRING_SEPARATOR); //分隔符
    pke_schema_table.append(table->s->db.str, table->s->db.length); //数据库名字 存入。
    pke_schema_table.append(HASH_STRING_SEPARATOR);//分隔符
    value_length= my_safe_itoa(10, table->s->db.length,
                               &value_length_buffer[VALUE_LENGTH_BUFFER_SIZE-1]); //存储的是字符形式的长度 返回为char指针 '1' '3' 代表 长度13 
    pke_schema_table.append(value_length);//将转换后的长度以字符串的方式存入
    pke_schema_table.append(table->s->table_name.str, table->s->table_name.length);//表名 字符存入。
    pke_schema_table.append(HASH_STRING_SEPARATOR);//分隔符
    value_length= my_safe_itoa(10, table->s->table_name.length,
                               &value_length_buffer[VALUE_LENGTH_BUFFER_SIZE-1]);//存储的是字符形式的长度 返回为char指针 '1' '3' 代表 长度13 
    pke_schema_table.append(value_length);//将转换后的长度以字符串的方式存入
    //因此上面的存储的为 分隔符+dbname+分隔符+dbname长度+分隔符+tablename+分隔符+tablename长度 这里就是代表了数据库和表信息
    std::string pke; //初始化pke 这是存储写集合元素hash前数据的中间变量
    pke.reserve(NAME_LEN * 5);

    char *pk_value= NULL;
    size_t pk_value_size= 0;

    // Buffer to read the names of the database and table names which is less
    // than 1024. So its a safe limit.
    char name_read_buffer[NAME_READ_BUFFER_SIZE];
    // Buffer to read the row data from the table record[0].
    String row_data(name_read_buffer, sizeof(name_read_buffer), &my_charset_bin); //读取当前行数据到buffer

#ifndef DBUG_OFF //如果没有定义 非DEBUG 模式
    std::vector<std::string> write_sets;
#endif

    for (uint key_number=0; key_number < table->s->keys; key_number++) //依次扫描每个索引   EXP:create table jj10(id1 int,id2 int,id3 int primary key,unique key(id1),key(id2));             
    {                                                                  //table->key_info[0].name  $12 = 0x7fffd8003631 "PRIMARY"  able->key_info[1].name $13 = 0x7fffd8003639 "id1"
      // Skip non unique.                                             //table->key_info[2].name $14 = 0x7fffd800363d "id2"
      if (!((table->key_info[key_number].flags & (HA_NOSAME )) == HA_NOSAME)) //跳过非唯一的KEY
        continue;

      /*
        To handle both members having hash values with and without collation
        in the same group, we generate and send both versions (with and without
        collation) of the hash in the newer versions. This would mean that a row
        change will generate 2 instead of 1 writeset, and 4 instead of 2, when PK
        are involved. This will mean that a transaction will be certified against
        two writesets instead of just one.

        To generate both versions (with and without collation) of the hash, it
        first converts using without collation support algorithm (old algorithm),
        and then using with collation support conversion algorithm, and adds
        generated value to key_list_to_hash vector, for hash generation later.

        Since the collation writeset is bigger or equal than the raw one, we do
        generate first the collation and reuse the buffer without the need to
        resize for the raw.
      */KEY_PART_INFO Field
      for (int collation_conversion_algorithm= COLLATION_CONVERSION_ALGORITHM;
           collation_conversion_algorithm >= 0;
           collation_conversion_algorithm--) //校队和非校队算法  也就是MySQL字段格式和字符串格式2种格式
      {
        pke.clear();
        pke.append(table->key_info[key_number].name); //table->key_info[0]  $15 = 0x7fffd8003631 "PRIMARY"
        pke.append(pke_schema_table);//将上面得到字符串写入 那么这里就是 主键 "primary + dbname+分隔符+dbname长度+分隔符+tablename+分隔符+tablename长度 "

        uint i= 0;
        for (/*empty*/; i < table->key_info[key_number].user_defined_key_parts; i++) //开始扫描每一个相应的字段
        {
          // read the primary key field values in str.
          int index= table->key_info[key_number].key_part[i].fieldnr; // TABLE  st_key  KEY_PART_INFO 字段在表中的相应位置
          size_t length= 0;

          /* Ignore if the value is NULL. */
          if (table->field[index-1]->is_null()) //Field **field;            /* Pointer to fields */   **point ->[*field,*field,*field...] 这里有多态每种字段类型有自己的各种算法
            break; //如果字段为空 或者 值为 空 返回

          // convert using collation support conversion algorithm
          if (COLLATION_CONVERSION_ALGORITHM == collation_conversion_algorithm) //如果采用校队算法
          {
            const CHARSET_INFO* cs= table->field[index-1]->charset();
            length= cs->coll->strnxfrmlen(cs,
                                       table->field[index-1]->pack_length()); //获取长度主键值
          }
          // convert using without collation support algorithm
          else
          {
            table->field[index-1]->val_str(&row_data);
            length= row_data.length();
          }

          if (pk_value_size < length+1)
          {
            pk_value_size= length+1;
            pk_value= (char*) my_realloc(key_memory_write_set_extraction,
                                         pk_value, pk_value_size,
                                         MYF(MY_ZEROFILL));
          }

          // convert using collation support conversion algorithm
          if (COLLATION_CONVERSION_ALGORITHM == collation_conversion_algorithm)
          {
            /*
              convert to normalized string and store so that it can be
              sorted using binary comparison functions like memcmp.
            */
            table->field[index-1]->make_sort_key((uchar*)pk_value, length); // 将字段的值存入到pk_value中,各种类型都有make_sort_key函数
            pk_value[length]= 0;
          }
          // convert using without collation support algorithm
          else
          {
            strmake(pk_value, row_data.c_ptr_safe(), length);
          }

          pke.append(pk_value, length); //将主键值计入
          pke.append(HASH_STRING_SEPARATOR);//分隔符
          value_length= my_safe_itoa(10, length,
                                     &value_length_buffer[VALUE_LENGTH_BUFFER_SIZE-1]);//存储的是字符形式的长度 返回为char指针 '1' '3' 代表 长度13 
          pke.append(value_length);//计入长度
        }

        /*
          If any part of the key is NULL, ignore adding it to hash keys.
          NULL cannot conflict with any value.
          Eg: create table t1(i int primary key not null, j int, k int,
                                                  unique key (j, k));
              insert into t1 values (1, 2, NULL);
              insert into t1 values (2, 2, NULL); => this is allowed.
        */
        if (i == table->key_info[key_number].user_defined_key_parts) //如果所有的索引字段都扫描完成
        {//最后得到的字符串为  非唯一索引名称+分隔符+库名+分隔符+库名长度+表名+分隔符+表名长度+索引字段1数值+分隔符 +索引字段1长度 [+ 索引字段2数值+分隔符 +索引字段2长度 .....]
          generate_hash_pke(pke, collation_conversion_algorithm, thd); //对pke内存空间做HASH  并且加入到写集合中
          writeset_hashes_added++; 
#ifndef DBUG_OFF
          write_sets.push_back(pke); //写入到write set 这是一个本地变量 非DEBUG环境没有
#endif
        }

作者微信:

微信.jpg

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
3月前
|
存储 JavaScript Java
(Python基础)新时代语言!一起学习Python吧!(四):dict字典和set类型;切片类型、列表生成式;map和reduce迭代器;filter过滤函数、sorted排序函数;lambda函数
dict字典 Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。 我们可以通过声明JS对象一样的方式声明dict
300 1
|
3月前
|
NoSQL 算法 Redis
【Docker】(3)学习Docker中 镜像与容器数据卷、映射关系!手把手带你安装 MySql主从同步 和 Redis三主三从集群!并且进行主从切换与扩容操作,还有分析 哈希分区 等知识点!
Union文件系统(UnionFS)是一种**分层、轻量级并且高性能的文件系统**,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem) Union 文件系统是 Docker 镜像的基础。 镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
589 5
|
4月前
|
关系型数据库 MySQL 数据管理
Mysql基础学习day03-作业
本内容包含数据库建表语句及多表查询示例,涵盖内连接、外连接、子查询及聚合统计,适用于员工与部门数据管理场景。
98 1
|
4月前
|
SQL 关系型数据库 MySQL
Mysql基础学习day01
本课程为MySQL基础学习第一天内容,涵盖MySQL概述、安装、SQL简介及其分类(DDL、DML、DQL、DCL)、数据库操作(查询、创建、使用、删除)及表操作(创建、约束、数据类型)。适合初学者入门学习数据库基本概念和操作方法。
215 6
|
4月前
|
SQL 关系型数据库 MySQL
Mysql基础学习day02-作业
本教程介绍了数据库表的创建与管理操作,包括创建员工表、插入测试数据、删除记录、更新数据以及多种查询操作,涵盖了SQL语句的基本使用方法,适合初学者学习数据库操作基础。
120 0
|
4月前
|
SQL 关系型数据库 MySQL
Mysql基础学习day03
本课程为MySQL基础学习第三天内容,主要讲解多表关系与多表查询。内容涵盖物理外键与逻辑外键的区别、一对多、一对一及多对多关系的实现方式,以及内连接、外连接、子查询等多表查询方法,并通过具体案例演示SQL语句的编写与应用。
143 0
|
4月前
|
SQL 关系型数据库 MySQL
Mysql基础学习day01-作业
本教程包含三个数据库表的创建练习:学生表(student)要求具备主键、自增长、非空、默认值及唯一约束;课程表(course)定义主键、非空唯一字段及数值精度限制;员工表(employee)包含自增主键、非空字段、默认值、唯一电话号及日期时间类型字段。每个表的结构设计均附有详细SQL代码示例。
105 0
|
4月前
|
SQL 关系型数据库 MySQL
Mysql基础学习day02
本课程为MySQL基础学习第二天内容,涵盖数据定义语言(DDL)的表查询、修改与删除操作,以及数据操作语言(DML)的增删改查功能。通过具体SQL语句与实例演示,帮助学习者掌握MySQL表结构操作及数据管理技巧。
178 0
|
SQL 存储 关系型数据库
【MySQL基础篇】全面学习总结SQL语法、DataGrip安装教程
本文详细介绍了MySQL中的SQL语法,包括数据定义(DDL)、数据操作(DML)、数据查询(DQL)和数据控制(DCL)四个主要部分。内容涵盖了创建、修改和删除数据库、表以及表字段的操作,以及通过图形化工具DataGrip进行数据库管理和查询。此外,还讲解了数据的增、删、改、查操作,以及查询语句的条件、聚合函数、分组、排序和分页等知识点。
1150 56
【MySQL基础篇】全面学习总结SQL语法、DataGrip安装教程
|
10月前
|
SQL 关系型数据库 MySQL
【YashanDB知识库】MySQL的FIND_IN_SET如何在YashanDB改写
本文来自YashanDB官网,探讨了MySQL中使用`FIND_IN_SET`函数在YashanDB中不兼容的问题及解决方法。由于YashanDB的`tinyint`类型无法参与条件运算,而MySQL的Boolean类型是`tinyint`的同义词,导致直接迁移时出现错误。文章通过分析原因,提出改写方案:将`where find_in_set(`替换为`where 0 &lt; find_in_set(`,从而实现兼容性调整,方便用户快速迁移和改写SQL语句。

推荐镜像

更多