深入理解MySQL 5.7 GTID系列(二):GTID相关内部数据结构

转载自:老叶茶馆(iMySQL_WX)

作者:高鹏(重庆八怪)

原文地址:http://www.jianshu.com/p/87f66cdeb49c

本次将陆续推出系列文章共十篇,本文使用的源码版本为percona 5.7.14,也比较过5.7.17,5.6.25的源码版本,暂时没有能力比较全部的MySQL源码版本,如有误导还请见谅。

一、 GTID基本格式

单个 GTID:

前一部分是SERVER_UUID,后面一部分是执行事务的唯一标志,通常是自增的。内部使用 GTID这种数据结构表示,后面会描述。

区间 GTID:

前一部分是SERVER_UUID,后面一部分是执行事务的唯一标志集合,在内部使用GTID_SET中某个SIDNO对应的INTERVAL节点表示,后面会描述。

二、SERVER_UUID的生成

既然说到了SERVER_UUID这里就开始讨论SERVER_UUID的生成。

SERVER_UUID实际上是一个32字节+1字节(/0)的字符串。MySQL启动的时候会调用INIT_SERVER_AUTO_OPTIONS()读取AUTO.CNF文件。如果没有读取到则调用GENERATE_SERVER_UUID()调用生成一个SERVER_ID。

实际上在这个函数里面会看到SERVER_UUID至少和下面部分有关:

MySQL启动时间

线程LWP有关

一个随机的内存地址有关

请看代码片段:

获得这些信息后会进入Item_func_uuid::val_str做运算返回,有兴趣的朋友可以深入看一下,最终会生成一个SERVER_UUID并且拷贝到实际的SERVER_UUID中如下:

调用栈帧:

三、SERVER_UUID的内部表示BINARY_LOG::UUID

BINARY_LOG::UUIDSERVER_UUID的内部表示实际上核心就是一个16字节的内存空间,如下:

SERVER_UUIDBINARY_LOG::UUID之间可以互相转换,在SID_MAP中BINARY_LOG::UUID表示的SERVER_UUID实际上就是其SID。

四、类结构GTID

本结构是单个GTID的内部表示其核心元素包括:

其中gno就是我们说的事务唯一标志,而SIDNO其实是SERVER_UUID的内部表示BINARY_LOG::UUID(SID)通过HASH算法得出的一个查找表中的值。参考函数SID_MAP::ADD_SID本函数则根据BINARY_LOG::UUID(SID)返回一个SIDNO。

五、类结构SID_MAP

既然说到了HASH算法那么需要一个内部结构来存储这种整个hash查找表,在MySQL中使用SID_MAP来作为这样一个结构,其中包含一个可变数组和一个HASH查找表其作用用来已经在注释里面给出。全局只有一个SID_MAP。会在GTID模块初始化的时候分配内存。SID_MAP核心元素如下:

这里在看一下可变数组中的指针元素NODE的类型:

其实他就是一个SIDNO和SID的对应。

六、类结构GTID_SET

本结构是一个关于某种类型GTID总的集合,比如我们熟知的有EXECUTE_GTID集合,PURGE_GTID集合。我们知道在一个EXECUTE_GTID集合中可能包含多个数据库的GTID也就是多个SIDNO同时存在的情况,并且可能存在某个数据库的GTID出现区间的情况如下:

这里3558703b-de63-11e7-91c3-5254008768e3的GNO出现了多个区间:

1-6

20-30

那么这种情况下内部表示应该是数组加区间链表的方式,当然MySQL内部正是这样实现的。我们来看核心元素:

7、GTID_SET的关联类结构INTERVAL

它正是前面说到的表示GTID区间如下:

他的内部类就是INTERVAL类结构我们看一下核心元素就懂了:

非常简单起始GNO加一个NEXT指针,标示了一个区间。

8、类结构GTID_STATE

本结构也是在数据库启动的时候和SID_MAP一起进行初始化,也是一个全局的变量。

我们熟知的参数几个参数如下:

GTID_EXECUTED

GTID_OWNED

GTID_PURGED

都来自于次,当然除了以上的我们常见的还包含了其他一些核心元素我们来具体看看:

9、类结构 OWNED_GTIDS

这个结构包含当前线程所包含的所有正在持有的GTID集合,为事务分配GTID会先将这个GTID和线程号加入到给OWNED_GTIDS然后将线程的THD->OWNED_GTID指向这个GTID。

参考函数GTID_STATE::ACQUIRE_OWNERSHIP,在COMMIT的最后阶段会将这个GTID从OWNED_GTIDS中移除参考函数OWNED_GTIDS::REMOVE_GTID并且将他加入到GTID_STATE::EXECUTED_GTIDS中。

这个过程会在后面进行描述。其核心元素包括:

这样一个每个SIDNO都会有HASH结构其HASH的内容则是:

这样一个结构体,我们看到其中包含了GNO和线程ID。

十、类结构GTID_TABLE_PERSISTOR

本结构主要是包含一些操作MySQL.GTID_EXECUTED表的函数接口

主要包含:

Insert the gtid into table.int save(THD *thd, const Gtid *gtid);

Insert the gtid set into table.int save(const Gtid_set *gtid_set);

Delete all rows from the table.int reset(THD *thd);

Fetch gtids from gtid_executed table and store them into

gtid_executed set.int fetch_gtids(Gtid_set *gtid_set);

Compress the gtid_executed table completely by employing one or more transactions.int compress(THD *thd);

Write a gtid interval into the gtid_executed table.

int write_row(TABLE *table, const char *sid,rpl_gno gno_start, rpl_gno gno_end);

Update a gtid interval in the gtid_executed table.

int update_row(TABLE *table, const char *sid,rpl_gno gno_start, rpl_gno new_gno_end);

Delete all rows in the gtid_executed table.int delete_all(TABLE *table);

这些方法也是确定什么时候读/写MySQL.GTID.EXECUTED的断点。

十一、内部结构图示

为了能够用图的方式解释这些类结构之间的关系,我修改MySQL.GTID_EXECUTED表和AUTO.CNF构造出了这种有区间的GTID案例,同时在源码处增加打印代码将启动完成后的get_executed_gtids/get_lost_gtids/get_gtids_only_in_table/get_previous_gtids_logged输出到了日志。但是在线上情况下很难见到这种有区间的GTID。

假设某一时刻我们数据库启动后各种GTID如下():

启动后我们马上执行了一个事务,这个事务正处于ORDERED_COMMIT的FLUSH阶段由GTID_STATE::ACQUIRE_OWNERSHIP获得了一个GTID那么它正在OWNED_GTIDS中,所以这个时候的图如下:

十二、本文小结

学习完本节至少能够学习到:

1、SERVER_UUID是什么,如何生成,按照什么规则生成

2、GTID内部是如何表示

3、SERVER_UUID和GTID内部表示之间的联系

4、 GTID_EXECUTED/GTID_OWNED/GTID_PURGED表示了什么具体对应哪个内存结构,当然这些将在后面的文章中多次提到,也会加深对它的了解。

如果有源码阅读能力的朋友可以按照这个框架继续深入学习。

  • 发表于:
  • 原文链接:http://kuaibao.qq.com/s/20180131B06CXS00?refer=cp_1026

扫码关注云+社区