共計 7670 個字符,預計需要花費 20 分鐘才能閱讀完成。
本篇內容介紹了“MySQL 寫集合是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓丸趣 TV 小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
一、什么是寫集合(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
}
“MySQL 寫集合是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注丸趣 TV 網站,丸趣 TV 小編將為大家輸出更多高質量的實用文章!