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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介:

水平有限,有误请谅解。

源码版本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

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
5天前
|
SQL 关系型数据库 MySQL
关于MySQL数据库的学习指南总结
【6月更文挑战第17天】MySQL是流行的关系型DBMS,适合各种应用。学习要点包括安装配置、数据类型、SQL(如SELECT、INSERT)、关系模型、表设计、SQL查询(如WHERE、ORDER BY)、事务处理、用户管理、性能优化和高级技术如存储过程、触发器。了解ACID特性,使用索引和内存优化提升性能,通过备份恢复确保数据安全。不断学习新技术以提升技能。
34 3
|
1月前
|
算法 Java 数据库连接
Spring+MySQL+数据结构+集合,Alibaba珍藏版mybatis手写文档
Spring+MySQL+数据结构+集合,Alibaba珍藏版mybatis手写文档
|
2天前
|
存储 JavaScript 前端开发
JavaScript进阶-Map与Set集合
【6月更文挑战第20天】JavaScript的ES6引入了`Map`和`Set`,它们是高效处理集合数据的工具。`Map`允许任何类型的键,提供唯一键值对;`Set`存储唯一值。使用`Map`时,注意键可以非字符串,用`has`检查键存在。`Set`常用于数组去重,如`[...new Set(array)]`。了解它们的高级应用,如结构转换和高效查询,能提升代码质量。别忘了`WeakMap`用于弱引用键,防止内存泄漏。实践使用以加深理解。
|
11天前
|
存储 安全 Java
Java集合详解:Set, Map, Vector, List的对比与联系
Java集合框架核心包括List、Set、Map和Vector。List允许重复元素,如ArrayList(适合读取)和LinkedList(适合插入删除)。Set不允许重复,有HashSet(无序)和TreeSet(排序)。Map存储键值对,HashMap(无序)和TreeMap(排序)。Vector是线程安全的ArrayList替代品,但在多线程环境下使用。选择集合类型应根据应用场景,如有序、无序、键值对需求及线程安全考虑。
|
14天前
|
存储 安全 Java
Java 集合(List、Set、Map 等)相关问答归纳再整理
HashMap 中使用键对象来计算 hashcode 值 HashSet 使用成员对象来计算 hashcode 值,对于两个对象来说hashcode 可能相同,所以 equals() 方法用来判断对象的相等性,如果两个对象不同的话,那么返回 false。 HashMap 比较快,因为是使用唯一的键来获取对象,HashSet 较 HashMap 来说比较慢。 4.1.3 HashMap 与 TreeMap
10 2
|
15天前
|
存储 Java 索引
JavaSE——集合框架一(6/7)-Set系列集合:LinkedHashSet的底层原理、TreeSet集合(介绍,自定义排序规则,排序示例)
JavaSE——集合框架一(6/7)-Set系列集合:LinkedHashSet的底层原理、TreeSet集合(介绍,自定义排序规则,排序示例)
18 1
|
15天前
|
存储 Java 索引
JavaSE——集合框架一(5/7)-Set系列集合:Set集合的特点、底层原理、哈希表、去重复原理
JavaSE——集合框架一(5/7)-Set系列集合:Set集合的特点、底层原理、哈希表、去重复原理
29 1
|
8天前
|
SQL 关系型数据库 MySQL
MySQL单表操作学习DDL_DML_DQL语句
MySQL单表操作学习DDL_DML_DQL语句
10 0
|
8天前
|
SQL 关系型数据库 MySQL
MySQL学习必备SQL_DDL_DML_DQL
MySQL学习必备SQL_DDL_DML_DQL
3 0
|
30天前
|
存储 Java
【JAVA学习之路 | 进阶篇】Set及其实现类与常用方法
【JAVA学习之路 | 进阶篇】Set及其实现类与常用方法