Mysql 5.7 Gtid内部学习(二) Gtid相关内部数据结构

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<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的对应。

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<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。

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中,所以这个时候的图如下:

未命名文件.png

11、本节小结

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

  • 1、server_uuid是什么,如何生成,按照什么规则生成
  • 2、Gtid内部是如何表示
  • 3、 server_uuid和Gtid内部表示之间的联系
  • 4、 gtid_executed/gtid_owned/gtid_purged表示了什么具体对应哪个内存结构,当然这些将在后面的文章中多次提到,也会加深对它的了解。

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

作者微信:

微信.jpg

时间: 2024-09-14 01:20:21

Mysql 5.7 Gtid内部学习(二) Gtid相关内部数据结构的相关文章

Mysql 5.7 Gtid内部学习(八) Gtid带来的运维改变

依托前文的解析来讲5.7中 Gtid带来的运维改变,我想理解应该是更加深刻,这节主要讨论以下几个部分: 如何跳过一个事务 mysqldump导出行为的改变 5.7中搭建基于Gtid的主从 5.7中Gtid的主从的切换 5.7中在线改变Gtid模式 一.如何跳过一个事务 和传统基于位置的主从不同,如果从库报错我们需要获得从库执行的最后一个事务,方法有如下: show slave status \G 中的 Executed_Gtid_Set. show global variables like '

Mysql 5.7 Gtid内部学习(三) Gtid和Last_commt/sequnce_number的生成时机

一.Gtid生成类型 这里首先使用源码的解释给出三种类型: AUTOMATIC_GROUP GTID_GROUP ANONYMOUS_GROUP 其中AUTOMATIC_GROUP通常用于主库开启Gtid的情况,GTID_GROUP通常用于备库和使用了GTID_NEXT的情况下. 源码中有详细解释如下: /** Specifies that the GTID has not been generated yet; it will be generated on commit. It will d

Mysql 5.7 Gtid内部学习(一) 导读

Mysql Gtid特性是5.6加入的一个强大的特性,它的目的在于使用Gtid的Mysql能够在整个复制环境中能够自动的切换,而不像以前需要指定文件和位置,这也一定是未来发展的方向,我们熟知的MGR也是基于Gtid的,所以了解Gtid的原理也是必要的. Gtid的维护是完全自动的,但是实际使用上确实有较多的坑,也导致很多朋友对Gtid还是觉得畏惧,本系列文章将从Gtid模块的源码出发分析,并且给出总结,然后结合运维和案例进行综合的解析,我希望抛砖引玉让希望了解源码的朋友也有所收获,但是能力有限特

Mysql 5.7 Gtid内部学习(十) 实际案例(二)

本案例是我真实遇到过的一个坑,也在前文中不止一次的提到,当时也是非常纳闷,其实知道原因后只能说为什么会这么坑. 一.触发条件 本案列我测试过4个版本 percona Mysql 5.7.14 官方社区 Mysql 5.7.17 percona Mysql 5.7.19 percona Mysql 5.7.15 其中percona Mysql 5.7.14和官方社区 Mysql 5.7.17有这个问题.其他版本未知 已知percona Mysql 5.7.14或者官方社区 Mysql 5.7.17

Mysql 5.7 Gtid内部学习(四) mysql.gtid_executed表Previous gtid Event的改变

之所以把mysql.gtid_executed表的作用和Previous gtid Event的改变放到一起进行描述是因为它们后面文章探讨的基础.这部分使用到了我自己使用C语言写的原生binlog解析工具infobin. 百度云盘下载如下:http://pan.baidu.com/s/1jHIWUN0 一.Gtid event 为什么要先描述什么是Gtid event呢?因为后面会用到,实际上在中其核心元素就是一个形如: 31704d8a-da74-11e7-b6bf-52540a7d243:1

Mysql 5.7 Gtid内部学习(五) mysql.gtid_executed表/gtid_executed变量/gtid_purged变量的更改时机

本节将集中讨论下面三种Gtid更新的时机,这部分相当重要,后面的故障案列会和这节有关.下面先来看一下他们的定义 mysql.gtid_executed表:Gtid持久化的介质,Mysql启动阶段会读取这个表来获取gtid_executed变量的值. gtid_executed变量(show global variables):Mysql数据库已经执行了哪些Gtid事务,处于内存中.show slave status中的Executed_Gtid_Set也取自这里. gtid_purged变量(s

Mysql 5.7 Gtid内部学习(九) 实际案例(一)

本案例是一个朋友的案例他也写了出来如下:https://mp.weixin.qq.com/s/XSnFkuYzIlGWMaXIl-oPeQ 但是和他交流后他也准备改因为分析有一些小问题. 一.触发条件 binlog_gtid_simple_recovery=false. 5.7.6以上版本. Gtid 关闭或者Gtid中途开启有大量的未开启Gtid的binlog. 二.本案例回顾 版本:MySQL版本 5.7.19. 故障为:大概每半小时发生一次故障,整个Mysql压力巨大,很多简单的操作都相应

MySQL的InnoDB引擎入门学习教程_Mysql

MySQL发展到今天,InnoDB引擎已经作为绝对的主力,除了像大数据量分析等比较特殊领域需求外,它适用于众多场景.然而,仍有不少开发者还在"执迷不悟"的使用MyISAM引擎,觉得对InnoDB无法把握好,还是MyISAM简单省事,还能支持快速COUNT(*).本文是由于最近几天帮忙处理discuz论坛有感而发,希望能对广大开发者有帮助. 1. 快速认识InnoDBInnoDB是MySQL下使用最广泛的引擎,它是基于MySQL的高可扩展性和高性能存储引擎,从5.5版本开始,它已经成为了

浅析MySQL的用户和权限学习总结

一.关于MySQL权限的几点常识: 1.MySQL的权限系统主要用来验证用户的操作权限. 2.在MySQL内部,权限信息存放在MySQL数据库的granttable里.当mysql启动后,granttable里的信息会写入内存. 3.MySQL 使用user name 加 host name 来作为标识符. 通过这种标识符,可以用来区分不同host上的相同的user name. 4.MySQL 权限控制有2种策略: 1)根据密码是否正确来控制客户端的连接. 2)假设可以正常connect,ser