一、 GTID基本格式
- 单个 GTID:
e859a28b-b66d-11e7-8371-000c291f347d:1
前一部分是SERVER_UUID,后面一部分是执行事务的唯一标志,通常是自增的。内部使用 GTID这种数据结构表示,后面会描述。
- 区间 GTID:
e859a28b-b66d-11e7-8371-000c291f347d:1-5
前一部分是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有关
- 一个随机的内存地址有关
请看代码片段:
const time_t save_server_start_time= server_start_time; //获取MySQL
启动时间
server_start_time+= ((ulonglong)current_pid << 48) + current_pid;//加入Lwp号运算
thd->status_var.bytes_sent= (ulonglong)thd;//这是一个内存指针
lex_start(thd);
func_uuid= new (thd->mem_root) Item_func_uuid();
func_uuid->fixed= 1;
func_uuid->val_str(&uuid); //这个函数里面有具体的运算过程
获得这些信息后会进入Item_func_uuid::val_str做运算返回,有兴趣的朋友可以深入看一下,最终会生成一个SERVER_UUID并且拷贝到实际的SERVER_UUID中如下:
strncpy(server_uuid, uuid.c_ptr(), UUID_LENGTH);
调用栈帧:
#0 init_server_auto_options () at /root/mysql5.7.14/percona-server-5.7.14-7/sql/mysqld.cc:3810
#1 0x0000000000ec625e in mysqld_main (argc=97, argv=0x2e9af08) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/mysqld.cc:4962
#2 0x0000000000ebd604 in main (argc=10, argv=0x7fffffffe458) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/main.cc:25
三、SERVER_UUID的内部表示BINARY_LOG::UUID
BINARY_LOG::UUID是SERVER_UUID的内部表示实际上核心就是一个16字节的内存空间,如下:
/** The number of bytes in the data of a Uuid. */
static const size_t BYTE_LENGTH= 16;
/** The data for this Uuid. */
unsigned char bytes[BYTE_LENGTH];
SERVER_UUID和BINARY_LOG::UUID之间可以互相转换,在SID_MAP中BINARY_LOG::UUID表示的SERVER_UUID实际上就是其SID。
四、类结构GTID
本结构是单个GTID的内部表示其核心元素包括:
/// SIDNO of this Gtid.
rpl_sidno sidno;
/// GNO of this Gtid.
rpl_gno gno;
其中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核心元素如下:
/// Read-write lock that protects updates to the number of SIDNOs.
mutable Checkable_rwlock *sid_lock;
/**
Array that maps SIDNO to SID; the element at index N points to a
Node with SIDNO N-1.
*/
Prealloced_array<Node*, 8, true>_sidno_to_sid; //因为sidno是一个连续的数值那么更具sidno找到sid只需要简单的做
//数组查找即可这里将node指针存入
/**
Hash that maps SID to SIDNO. The keys in this array are of type
rpl_sid.
*/
HASH _sid_to_sidno; //因为sid是一个数据结构其核心为bytes关键值存储了16字节根据server_uuid
//转换而来的无规律内存空间,需要使用hash查找表快速定位
/**
Array that maps numbers in the interval [0, get_max_sidno()-1] to
SIDNOs, in order of increasing SID.
@see Sid_map::get_sorted_sidno.
*/
Prealloced_array<rpl_sidno, 8, true> _sorted;//额外的一个关于sidno的数组,具体作用未知
这里在看一下可变数组中的指针元素NODE的类型:
struct Node
{
rpl_sidno sidno; //sid hash no
rpl_sid sid; //sid
};
其实他就是一个SIDNO和SID的对应。
六、类结构GTID_SET
本结构是一个关于某种类型GTID总的集合,比如我们熟知的有EXECUTE_GTID集合,PURGE_GTID集合。我们知道在一个EXECUTE_GTID集合中可能包含多个数据库的GTID也就是多个SIDNO同时存在的情况,并且可能存在某个数据库的GTID出现区间的情况如下:
| gtid_executed |
3558703b-de63-11e7-91c3-5254008768e3:1-6:20-30,
da267088-9c22-11e7-ab56-5254008768e3:1-34 |
这里3558703b-de63-11e7-91c3-5254008768e3的GNO出现了多个区间:
- 1-6
- 20-30
那么这种情况下内部表示应该是数组加区间链表的方式,当然MySQL内部正是这样实现的。我们来看核心元素:
/**
Array where the N'th element contains the head pointer to the
intervals of SIDNO N+1.
*/
Prealloced_array<Interval*, 8, true> m_intervals;//每个sidno 包含一个Interval 单项链表,由next指针连接 这就完成了比如分割GTID的问题
/// Linked list of free intervals.
Interval *free_intervals; //空闲的interval连接在这个链表上
/// Linked list of chunks.
Interval_chunk *chunks; //全局的一个Interval 链表 所有的Interval空间都连接在上面
7、GTID_SET的关联类结构INTERVAL
它正是前面说到的表示GTID区间如下:
- da267088-9c22-11e7-ab56-5254008768e3:1-34
他的内部类就是INTERVAL类结构我们看一下核心元素就懂了:
/// The first GNO of this interval.
rpl_gno start;
/// The first GNO after this interval.
rpl_gno end;
/// Pointer to next interval in list.
Interval *next;
非常简单起始GNO加一个NEXT指针,标示了一个区间。
8、类结构GTID_STATE
本结构也是在数据库启动的时候和SID_MAP一起进行初始化,也是一个全局的变量。
我们熟知的参数几个参数如下:
- GTID_EXECUTED
- GTID_OWNED
- GTID_PURGED
都来自于次,当然除了以上的我们常见的还包含了其他一些核心元素我们来具体看看:
/// The Sid_map used by this Gtid_state.
mutable Sid_map *sid_map; //使用sid_map
/**
The set of GTIDs that existed in some previously purged binary log.
This is always a subset of executed_gtids.
*/
Gtid_set lost_gtids; //对应gtid_purged参数,这个参数一般由Mysql自动维护除非手动设置了gtid_purged参数
/*
The set of GTIDs that has been executed and
stored into gtid_executed table.
*/
Gtid_set executed_gtids; //对应gtid_executed参数,这个参数一般由Mysql主动维护
/*
The set of GTIDs that exists only in gtid_executed table, not in
binlog files.
*/
Gtid_set gtids_only_in_table;
//正常来讲对于主库这个集合始终为空因为主库不可能存在只在mysql.gtid_executed表而不再binlog中的gtid,但是从库则必须开启log_slave_updates和binlog才会达到这个效果,
//否则binlog不包含relay的Gtid的只能包含在mysql.gtid_executed表中,那么这种情况下Gtid_set gtids_only_in_table是始终存在的。具体后面还会解释。
/* The previous GTIDs in the last binlog. */
Gtid_set previous_gtids_logged;//包含上一个binlog已经执行的所有的在binlog的Gtid
/// The set of GTIDs that are owned by some thread.
Owned_gtids owned_gtids;//当前所有线程拥有的全部Gtid集合
/// The SIDNO for this server.
rpl_sidno server_sidno;//就是服务器server_uuid对应sid hash出来的sidno
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中。
这个过程会在后面进行描述。其核心元素包括:
/// Growable array of hashes.
Prealloced_array<HASH*, 8, true> sidno_to_hash;
这样一个每个SIDNO都会有HASH结构其HASH的内容则是:
struct Node
{
/// GNO of the group.
rpl_gno gno;
/// Owner of the group.
my_thread_id owner;
};
这样一个结构体,我们看到其中包含了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 intogtid_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.
- write_row(TABLE table, const char sid,rpl_gno gno_start, rpl_gno gno_end);
- Update a gtid interval in the gtid_executed table.
- 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如下():
- 2017-12-12T04:10:42.768153Z 0 [Note] gtid_state->get_executed_gtids:
Gtid have:3558703b-de63-11e7-91c3-5254008768e3:1-35,
da267088-9c22-11e7-ab56-5254008768e3:1-34
- 2017-12-12T04:10:42.768191Z 0 [Note] gtid_state->get_lost_gtids:
Gtid have:3558703b-de63-11e7-91c3-5254008768e3:1-6:20-30,
da267088-9c22-11e7-ab56-5254008768e3:1-34
- 2017-12-12T04:10:42.768226Z 0 [Note] gtid_state->get_gtids_only_in_table:
Gtid have:3558703b-de63-11e7-91c3-5254008768e3:1-6:20-30,
da267088-9c22-11e7-ab56-5254008768e3:1-34
- 2017-12-12T04:10:42.768260Z 0 [Note] gtid_state->get_previous_gtids_logged:Gtid have:3558703b-de63-11e7-91c3-5254008768e3:7-19:31-35
启动后我们马上执行了一个事务,这个事务正处于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表示了什么具体对应哪个内存结构,当然这些将在后面的文章中多次提到,也会加深对它的了解。
原文发布时间为:2018-01-09
本文作者:高鹏(重庆八怪)
本文来自云栖社区合作伙伴“老叶茶馆”,了解相关信息可以关注“老叶茶馆”微信公众号