本篇内容介绍了“MySQL写集合是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
一、什么是写集合(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; 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 (; 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;
if (table->field[index-1]->is_null()) //Field **field; **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)
{
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 (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写集合是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!