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
相关文章
|
存储 关系型数据库 MySQL
|
1月前
|
监控 算法 安全
带用集合算法set union讲解
带用集合算法set union讲解
20 0
|
存储 关系型数据库 MySQL
|
21天前
|
SQL 存储 关系型数据库
6本值得推荐的MySQL学习书籍
本文是关于MySQL学习书籍的推荐,作者在DotNetGuide技术社区和微信公众号收到读者请求后,精选了6本值得阅读的MySQL书籍,包括《SQL学习指南(第3版)》、《MySQL是怎样使用的:快速入门MySQL》、《MySQL是怎样运行的:从根儿上理解MySQL》、《深入浅出MySQL:数据库开发、优化与管理维护(第3版)》以及《高性能MySQL(第4版)》和《MySQL技术内幕InnoDB存储引擎(第2版)》。此外,还有12本免费书籍的赠送活动,涵盖《SQL学习指南》、《MySQL是怎样使用的》等,赠书活动有效期至2024年4月9日。
|
27天前
|
SQL 关系型数据库 MySQL
轻松入门MySQL:深入学习数据库表管理,创建、修改、约束、建议与性能优化(3)
轻松入门MySQL:深入学习数据库表管理,创建、修改、约束、建议与性能优化(3)
|
1天前
|
SQL 监控 关系型数据库
【MySQL学习】MySQL的慢查询日志和错误日志
【MySQL学习】MySQL的慢查询日志和错误日志
|
7天前
|
存储 SQL 关系型数据库
MySQL学习手册(第一部分)
mysql日常使用记录
63 0
|
14天前
|
程序员 索引 Python
06-python数据容器-set(集合)入门基础操作
06-python数据容器-set(集合)入门基础操作
|
存储 SQL 关系型数据库
|
1月前
|
存储 Shell Linux
【Shell 命令集合 系统设置 内置命令】⭐⭐⭐Linux 设置或修改shell环境变量set命令 使用指南
【Shell 命令集合 系统设置 内置命令】⭐⭐⭐Linux 设置或修改shell环境变量set命令 使用指南
33 0