共计 7397 个字符,预计需要花费 19 分钟才能阅读完成。
本篇内容介绍了“Mysql 5.7 中 Gtid 相关内部数据结构有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让丸趣 TV 小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
1、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 节点表示,后面会描述。
2、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 至少和下面部分有关:
1、mysql 启动时间
2、线程 Lwp 有关
3、一个随机的内存地址有关
请看代码片段:
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
3、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。
4、类结构 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。
5、类结构 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_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 _sorted;// 额外的一个关于 sidno 的数组,具体作用未知
这里在看一下可变数组中的指针元素 Node 的类型:
struct Node
{
rpl_sidno sidno; //sid hash no
rpl_sid sid; //sid
};
其实他就是一个 sidno 和 sid 的对应。
6、类结构 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 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 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。
10、类结构 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 的断点。
10、内部结构图示
为了能够用图的方式解释这些类结构之间的关系,我修改 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 中,所以这个时候的图如下:
“Mysql 5.7 中 Gtid 相关内部数据结构有哪些”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注丸趣 TV 网站,丸趣 TV 小编将为大家输出更多高质量的实用文章!