共计 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 小编将为大家输出更多高质量的实用文章!