MySQL:MGR 學習(1):寫集合(Write set)

gaopengtttt發表於2018-09-12

水平有限,有誤請諒解。
原始碼版本5.7.22


一、什麼是寫集合(Write set)

實際上寫集合定義在類Rpl_transaction_write_set_ctx中,其中主要包含兩個資料結構

  • std::vector<uint64> write_set;

  • std::set<uint64> 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=latin11 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 並且加入到寫集合中#endif
        }

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7728585/viewspace-2214038/,如需轉載,請註明出處,否則將追究法律責任。

相關文章