千家信息网

Mysql 5.7 Gtid内部学习(六) Mysql启动初始化Gtid模块

发表于:2026-10-31 作者:千家信息网编辑
千家信息网最后更新 2026年10月31日,简书地址:http://www.jianshu.com/p/fc836446cde0本节也是一个重头戏,后面的故障案例也和本节有关。本节将详细介绍Gtid模块的初始化,以及什么时候读取了我们前文提及的
千家信息网最后更新 2026年10月31日Mysql 5.7 Gtid内部学习(六) Mysql启动初始化Gtid模块

简书地址:
http://www.jianshu.com/p/fc836446cde0

本节也是一个重头戏,后面的故障案例也和本节有关。本节将详细介绍Gtid模块的初始化,以及什么时候读取了我们前文提及的两个Gtid持久化介质:

  • binlog文件
  • mysql.gtid_executed表

此外也会描述他们的读取方式。
同时分析这个步骤我也将在重点步骤分为两种情况来分别讨论:

  1. 主库开启Gtid开启binlog。
  2. 从库开启Gtid开启binlog不开启log_slave_updates参数。

因为这两种使我们通常设置的方式,下面简称主库和从库。

一、初始化Gtid 模块全局变量内存空间

首先初始化Gtid 几个Global 内存空间包括 Gtid_state\Sid_map\gtid_table_persistor
这个调用由mysqld.cc调入gtid_server_init()。

 if (init_server_components())    unireg_abort(MYSQLD_ABORT_EXIT); 

其中init_server_components()会初始化很多模块Gtid只是其中很小的一个,Innodb就在这里初始化。

gtid_server_init()函数片段如下:

(!(global_sid_lock= new Checkable_rwlock(#ifdef HAVE_PSI_INTERFACE                                             key_rwlock_global_sid_lock#endif                                            )) ||     !(gtid_mode_lock= new Checkable_rwlock(#ifdef HAVE_PSI_INTERFACE                                            key_rwlock_gtid_mode_lock#endif                                           )) ||     !(global_sid_map= new Sid_map(global_sid_lock)) || //new一个内存Sid_map内存空间出来     !(gtid_state= new Gtid_state(global_sid_lock, global_sid_map))||//new一个内存Gtid_state内存空间出来     !(gtid_table_persistor= new Gtid_table_persistor()));//new一个内存Gtid_table_persistor内存空间出来 

二、初始化获得本数据库的server_uuid

这个初始化过程在前文提到了,无非就是通过my.cnf获得server_uuid,如果没有则重新生成,具体可以参考一下前文这里不再过多描述。

 if (init_server_auto_options())  {    sql_print_error("Initialization of the server's UUID failed because it could"                    " not be read from the auto.cnf file. If this is a new"                    " server, the initialization failed because it was not"                    " possible to generate a new UUID.");    unireg_abort(MYSQLD_ABORT_EXIT);  } 

三、初始化Gtid_state全局结构

 global_sid_lock->rdlock();  int gtid_ret= gtid_state->init();//将server_uuid对应的sid(Uuid)和sidno加入到Sid_map中。  global_sid_lock->unlock();  if (gtid_ret)    unireg_abort(MYSQLD_ABORT_EXIT); 

其实本步骤也是完成了sidno的加入Sid_map中,有兴趣的可以参考int Gtid_state::init()函数逻辑非常简单。

四、读取mysql.gtid_executed表

这一步开始读取我们的第一个Gtid持久化介质mysql.gtid_executed表,其最终调用为Gtid_table_persistor::fetch_gtids(Gtid_set *gtid_set)其原理为一行一行的读取mysql.gtid_executed表的内容加入到Gtid_state.executed_gtids中,我们来看源码:

 // Initialize executed_gtids from mysql.gtid_executed table.  if (gtid_state->read_gtid_executed_from_table() == -1)    unireg_abort(1); 

Gtid_state::read_gtid_executed_from_table只是一层简单的封装如下:

int Gtid_state::read_gtid_executed_from_table(){  return gtid_table_persistor->fetch_gtids(&executed_gtids);} 

接下来看看Gtid_table_persistor::fetch_gtids(Gtid_set *gtid_set)函数逻辑片段

 if ((err= table->file->ha_rnd_init(true)))  {    ret= -1;    goto end;  }  while(!(err= table->file->ha_rnd_next(table->record[0]))) //开始一行一行读取数据  {    /* Store the gtid into the gtid_set */    /**      @todo:      - take only global_sid_lock->rdlock(), and take        gtid_state->sid_lock for each iteration.      - Add wrapper around Gtid_set::add_gno_interval and call that        instead.    */    global_sid_lock->wrlock();    if (gtid_set->add_gtid_text(encode_gtid_text(table).c_str()) !=  //此处将读取到的一行Gtid区间加入到Gtid_state.executed_gtids中。        RETURN_STATUS_OK)    {      global_sid_lock->unlock();      break;    }    global_sid_lock->unlock();  } 

完成本步骤过后Gtid_state.executed_gtids将设置,主库和从库的设置不同

  • 主库因为mysql.gtid_executed只包含上一个binlog的全部Gtid所以,它不包含最新binlog的Gtid,所以在第七步还需要修正这个值。
  • 从库因为mysql.gtid_executed会实时更新,因此它包含了全部的Gtid。

五、定义中间变量和获得指针

本步骤是一个非关键步骤但是定义了一些中间变量而且定义了4个指针来分别获得Gtid_state四个内存变量的地址,方便操作。

 if (opt_bin_log) //如果binlog开启  {    /*      Initialize GLOBAL.GTID_EXECUTED and GLOBAL.GTID_PURGED from      gtid_executed table and binlog files during server startup.    */    Gtid_set *executed_gtids=      const_cast(gtid_state->get_executed_gtids());//获得Gtid_state.executed_gtids的指针    Gtid_set *lost_gtids=      const_cast(gtid_state->get_lost_gtids());//获得gtid_state.get_lost_gtids的指针    Gtid_set *gtids_only_in_table=      const_cast(gtid_state->get_gtids_only_in_table());//获得gtid_state.get_lost_gtids的指针    Gtid_set *previous_gtids_logged=      const_cast(gtid_state->get_previous_gtids_logged());//获得gtid_state.previous_gtids_logged的指针    Gtid_set purged_gtids_from_binlog(global_sid_map, global_sid_lock);//定义临时变量用于存储从binlog中扫描到已经丢弃的Gtid事物。    Gtid_set gtids_in_binlog(global_sid_map, global_sid_lock);//定义中间变量binlog中包含的所有Gtid事物包括丢弃的。    Gtid_set gtids_in_binlog_not_in_table(global_sid_map, global_sid_lock);//定义中间变量没有存放在表中而在binlog中存在过的Gtid事物,//显然主库包含这样一个集合,因为主库的gtids_in_binlog>gtids_only_in_table,而从库同样也不包含这样一个集合因为从库的全部Gtid事物都在表中。 

六、读取binlog文件

本步骤将会读取我们提及的第二个Gtid持久化介质binlog,其读取方式为先反向读取获得 gtids_in_binlog然后正向读取获得 purged_gtids_from_binlog,并且这里正向读取purged_gtids_from_binlog将会受到binlog_gtid_simple_recovery参数的影响。同时我们前文所描述5.7 中Previous gtid Event会在没有开启Gtid的binlog也包含这个event,将在这部体现出它的价值。

if (mysql_bin_log.init_gtid_sets(>ids_in_binlog,                                     &purged_gtids_from_binlog,                                     opt_master_verify_checksum,                                     true/*true=need lock*/,                                     NULL/*trx_parser*/,                                     NULL/*gtid_partial_trx*/,                                     true/*is_server_starting*/)) 

我们发现他实际上就是调用bool MYSQL_BIN_LOG::init_gtid_sets()函数我们继续看这个函数重要代码片段:

 list filename_list; //定义一个string list来存储文件名  LOG_INFO linfo;  int error;  list::iterator it;//定义一个list的正向迭代器  list::reverse_iterator rit;//定义一个list的反向迭代器for (error= find_log_pos(&linfo, NULL, false/*need_lock_index=false*/); !error; //这部分实际上就是将文件名全部加入到这个list中       error= find_next_log(&linfo, false/*need_lock_index=false*/))  {    DBUG_PRINT("info", ("read log filename '%s'", linfo.log_file_name));    filename_list.push_back(string(linfo.log_file_name));  }  if (error != LOG_INFO_EOF)  {    DBUG_PRINT("error", ("Error reading %s index",                         is_relay_log ? "relaylog" : "binlog"));    goto end;  } if (all_gtids != NULL) //数据库启动初始化的情况下all_gtids不会为NULL,但是如果是做purge binary logs命令等删除binlog log all_gtid会传入NULL  {    rit= filename_list.rbegin(); //反向迭代器指向list尾部    bool can_stop_reading= false;    reached_first_file= (rit == filename_list.rend());//如果只有一个binlog则为true    while (!can_stop_reading && !reached_first_file) //开始反向循环扫描来获得gtids_in_binlog(all_gtids)集合    {      const char *filename= rit->c_str(); //获取文件名      rit++;      reached_first_file= (rit == filename_list.rend());//如果达到第一个文件则为true表示扫描完成      switch (read_gtids_from_binlog(filename, all_gtids,                                     reached_first_file ? lost_gtids : NULL,                                     NULL/* first_gtid */,                                     sid_map, verify_checksum, is_relay_log)) //通过函数read_gtids_from_binlog读取这个binlog文件      {        case ERROR:        {          error= 1;          goto end;        }        case GOT_GTIDS:  //如果扫描本binlog有PREVIOUS GTID EVENT和GTID EVENT 则break 跳出循环且设置can_stop_reading= true        {          can_stop_reading= true;          break;        }        case GOT_PREVIOUS_GTIDS://如果扫描本binlog只有PREVIOUS GTID EVENT 则进入逻辑判断        {          if (!is_relay_log)//我们只考虑binlog 不会是relaylog 那么 break 跳出循环且设置can_stop_reading= true,                            //注意这里并不受到binlog_gtid_simple_recovery参数的影响,我们知道5.7.5过后每一个binlog都                            //包含了PREVIOUS GTID EVENT实际上即使没有开启GTID这里也会跳出循环,则只是扫描了最后一个binlog 文件            can_stop_reading= true;          break;        }        case NO_GTIDS: //如果没有找到PREVIOUS GTID EVENT和GTID EVENT 则做如下逻辑,实际上5.7过后不可能出现这种问题,因为必然包含了PREVIOUS GTID EVENT                       //即便是没有开启GTID,所以反向查找一定会在扫描最后一个文件后跳出循环        {          if (binlog_gtid_simple_recovery && is_server_starting &&              !is_relay_log) //这里受到了binlog_gtid_simple_recovery参数的影响,但是我们知道这个分支是不会执行的。除非这个数据库是升级的并且没有开启Gtid          {            DBUG_ASSERT(all_gtids->is_empty());//断言all_gtids还是没有找到            DBUG_ASSERT(lost_gtids->is_empty());//断言lost_gtids还是没有找到            goto end;//结束扫描,从这里我们发现如果mysql是升级而来的一定要注意这个问题,设置binlog_gtid_simple_recovery可能拿不到正确的GTID,对于升级                     //最好使用master-slave 进行升级,可以规避这个风险。          }          /*FALLTHROUGH*/        }        case TRUNCATED:        {          break;        }      }    }//中间还有一部分处理relaylog的占时没有去研究接下来就是正向查找获得purged_gtids_from_binlog(lost_gtids) if (lost_gtids != NULL && !reached_first_file)//如果前面的扫描没有扫描完全部的binlog,这实际在5.7中是肯定的。  {       for (it= filename_list.begin(); it != filename_list.end(); it++)//进行正向查找    {      /*        We should pass a first_gtid to read_gtids_from_binlog when        binlog_gtid_simple_recovery is disabled, or else it will return        right after reading the PREVIOUS_GTIDS event to avoid stall on        reading the whole binary log.      */      Gtid first_gtid= {0, 0};      const char *filename= it->c_str();//获得文件名指针      switch (read_gtids_from_binlog(filename, NULL, lost_gtids,                                     binlog_gtid_simple_recovery ? NULL :                                                                   &first_gtid,                                     sid_map, verify_checksum, is_relay_log))      {        case ERROR:        {          error= 1;          /*FALLTHROUGH*/        }        case GOT_GTIDS: //如果扫描本binlog有PREVIOUS GTID EVENT和GTID EVENT 则跳出循环直达end        {          goto end;        }        case NO_GTIDS: //这里如果binlog不包含GTID EVENT和PREVIOUS GTID EVENT其处理逻辑一致        case GOT_PREVIOUS_GTIDS:        {          if (binlog_gtid_simple_recovery) //这里受到了binlog_gtid_simple_recovery。如果设置为ON,实际上在5.7过后            goto end;                      //PREVIOUS GTID EVENT是一定命中的,可以得到正确的结果,但是如果是5.6升级而来          /*FALLTHROUGH*/                  //则binlog不包含PREVIOUS GTID EVENT则purged_gtids_from_binlog(lost_gtids)获取为空                                           //如果在5.7中关闭了GTID,这种情况这里虽然PREVIOUS GTID EVENT命中但是任然                                           //不会跳出循环goto end,继续下一个文件扫描。        }                                          case TRUNCATED:        {          break;        }      }    } 

到这里我们分析了反向查找和正向查找,我们代码注释上也说明了binlog_gtid_simple_recovery作用,因为有了PREVIOUS GTID EVENT的支持,5.7.6过后这个参数默认都是设置为true,如果在Gtid关闭的情况下设置binlog_gtid_simple_recovery为flase可能需要扫描大量的binlog才会确定purged_gtids_from_binlog这个集合,这可能出现在两个地方:

  • 如这里讨论的Mysql重启的时候。
  • 如前文所讨论在purge binary logs to或者操作参数expire_logs_days设置的时间删除binlog的时候。

这里也是我后文描述的第二个案例出现的原因。

正常情况下到这里我们的gtids_in_binlog和purged_gtids_from_binlog已经获取:

  • 主库gtids_in_binlog包含了所有最新的Gtid事物(包含丢弃的)而purged_gtids_from_binlog包含了已经丢弃的Gtid事物。
  • 从库压根没有binlog因此gtids_in_binlog和purged_gtids_from_binlog为空集合。

七、对Gtid_state.executed_gtids和mysql.gtid_executed表的修正

如第四步描述主库通过读取mysql.gtid_executed表获得的Gtid_state.executed_gtids并不是最新的,所以整理需要修正,代码如下:

 if (!gtids_in_binlog.is_empty() && //如果gtids_in_binlog不为空,从库为空不走这个逻辑了,这里主要是主库对Gtid_state.executed_gtids的修正        !gtids_in_binlog.is_subset(executed_gtids)) //并且executed_gtids是gtids_in_binlog的子集      {      gtids_in_binlog_not_in_table.add_gtid_set(>ids_in_binlog);      if (!executed_gtids->is_empty())        gtids_in_binlog_not_in_table.remove_gtid_set(executed_gtids);   //将不在表中的GTID及gtids_in_binlog-executed_gtids 加入到gtids_in_binlog_not_in_table    if (gtid_state->save(>ids_in_binlog_not_in_table) == -1)//这里将gtids_in_binlog_not_in_table这个Gtid集合存储到mysql.gtid_executed表中完成修正      {        global_sid_lock->unlock();        unireg_abort(MYSQLD_ABORT_EXIT);      }      executed_gtids->add_gtid_set(>ids_in_binlog_not_in_table);//最后在executed_gtids中加入这个gtids_in_binlog_not_in_table,这个完成executed_gtids就是最新的Gtid_set了,完成了Gtid_state.executed_gtids的修正    } 

这一步完全是主库才会触发的逻辑:

  • 主库完成这一步Gtid_state.executed_gtids和mysql.gtid_executed表都将修正到最新的Gtid集合。
  • 从库不做这个逻辑,因为从库的Gtid_state.executed_gtids和mysql.gtid_executed表本来就是最新的。

到这里Gtid_state.executed_gtids也就是我们的gtid_executed变量初始化已经完成mysql.gtid_executed表已经修正。

八、初始化Gtid_state.gtids_only_in_table

由于上一步已经获得了完整的的Gtid_state.executed_gtids 集合,这里获得Gtid_state.gtids_only_in_table只需要简单的gtids_only_in_table= executed_gtids - gtids_in_binlog相减即可。

/* gtids_only_in_table= executed_gtids - gtids_in_binlog */    if (gtids_only_in_table->add_gtid_set(executed_gtids) !=  //这里将executed_gtids加入到gtids_only_in_table        RETURN_STATUS_OK)    {      global_sid_lock->unlock();      unireg_abort(MYSQLD_ABORT_EXIT);    }    gtids_only_in_table->remove_gtid_set(>ids_in_binlog); //这里将去掉gtids_in_binlog 

这一步主库和从库如下:

  • 主库由于存在Gtid_state.executed_gtids是最新的同时gtids_in_binlog也是最新的所以Gtid_state. gtids_only_in_table是一个空集合。
  • 从库由于Gtid_state.executed_gtids是最新的但是gtids_in_binlog 是一个空集合,所以Gtid_state. gtids_only_in_table和Gtid_state.executed_gtids相等。

九、初始化Gtid_state.lost_gtids

这一步开始获取Gtid_state.lost_gtids也就是我们的gtid_purged变量,这里只需要简单的用Gtid_state.gtids_only_in_table + purged_gtids_from_binlog;即可,他们都已经获取

 /*      lost_gtids = executed_gtids -                   (gtids_in_binlog - purged_gtids_from_binlog)                 = gtids_only_in_table + purged_gtids_from_binlog;    */ if (lost_gtids->add_gtid_set(gtids_only_in_table) != RETURN_STATUS_OK || //将gtids_only_in_table这个集合加入lost_gtids        lost_gtids->add_gtid_set(&purged_gtids_from_binlog) !=            //将purged_gtids_from_binlog加入到这个集合        RETURN_STATUS_OK)    {      global_sid_lock->unlock();      unireg_abort(MYSQLD_ABORT_EXIT);    } 

这一步主库和从库如下:

  • 主库由于Gtid_state.gtids_only_in_table为空集合,而purged_gtids_from_binlog则是获取的第一个binlog Previous gtid Event的Gtid。所以正常情况下Gtid_state.lost_gtids就等于第一个binlog的binlog Previous gtid Event 的Gtid。
  • 从库由于Gtid_state.gtids_only_in_table和Gtid_state.executed_gtids相等而purged_gtids_from_binlog是空集合,所以正常情况下从库的Gtid_state.lost_gtids就等于就等于Gtid_state.executed_gtids。也就是gtid_purged变量和gtid_executed变量相等。

到这里gtid_purged变量和gtid_executed变量以及mysql.gtid_executed表都已经初始化完成。

十、初始化Gtid_state.previous_gtids_logged

这个值没有变量能够看到,它代表是直到上一个binlog所包含的全部的binlog Gtid。

/* Prepare previous_gtids_logged for next binlog */    if (previous_gtids_logged->add_gtid_set(>ids_in_binlog) !=//很明显将扫描到的gtids_in_binlog的这个集合加入即可。        RETURN_STATUS_OK)    {      global_sid_lock->unlock();      unireg_abort(MYSQLD_ABORT_EXIT);    } 

很明显因为启动的时候binlog会切换所以简单的将扫描到gtids_in_binlog加入到集合即可。
这一步主库和从库如下:

  • 主库gtids_in_binlog包含全部Gtid事物。所以gtid_state.previous_gtids_logged就包含全部binlog中的Gtid事物。但是之后会做binlog切换。
  • 从库gtids_in_binlog为空,显然gtid_state.previous_gtids_logged也为空。

十一、本节小结

通过读取mysql.gtid_executed和binlog,然后经过一系列的运算后,我们的Gtid模块初始化完成。4个内存变量和mysql.gtid_executed都得到了初始化,总结如下:

  1. mysql.gtid_executed表
  • 主库在第四步读取,在第七步的修正完成初始化,它包含了现有的全部的Gtid事物。
  • 从库在第四步读取,因为从库mysql.gtid_executed本来就是最新的不需要更改。
  1. Gtid_state.executed_gtids和它对应了变量gtid_executed
  • 主库通过第四步和第七步将Gtid_state.executed_gtids初始化,它包含了全部的Gtid事物。
  • 从库通过第四步就已经全部初始化完成,它包含了现有的全部的Gtid事物。
  1. Gtid_state.gtids_only_in_table
  • 主库通过第八步进行初始化,主库Gtid_state. gtids_only_in_table是一个空集合。
  • 从库通过第八步进行初始化,从库Gtid_state. gtids_only_in_table和Gtid_state.executed_gtids相等。
  1. Gtid_state.lost_gtids它对应了变量gtid_purged。
  • 主库通过第九步进行初始化,Gtid_state.lost_gtids就是第一个binlog Previous gtid Event的Gtid。
  • 主库通过第九步进行初始化,Gtid_state.lost_gtids等于Gtid_state.executed_gtids
  1. Gtid_state.previous_gtids_logged。
  • 主库通过第十步进行初始化,gtid_state.previous_gtids_logged就包含全部binlog中的Gtid事物
  • 从库通过第十步进行初始化,gtid_state.previous_gtids_logged为空集合。

注意本节第五步包含了binlog文件的读取方法以及binlog_gtid_simple_recovery参数的作用

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

  • 1、mysql.gtid_executed是如何以及何时读取的。
  • 2、binlog 文件中的Gtid何时读取。
  • 3、整个Gtid模块的初始化流程及细节。
  • 4、binlog_gtid_simple_recovery参数的作用。

作者微信:


变量 文件 事物 内存 参数 就是 逻辑 情况 指针 步骤 循环 函数 实际 模块 一行 实际上 空间 过后 前文 升级 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网核心科技是什么 齐家互联网科技 易通互联网络科技有限公司 睿民互联网科技 米客互联网科技 互联网科技汛 重庆科技学院互联网 互联网科技股午盘股票涨跌 湖南洣水互联网科技有限公司扣款 银川新华互联网科技学校地址 成都玖崟互联网科技有限公司乐商圈怎么样 互联网金融科技公司干啥的 栖莱科技互联网股份有限公司 哈尔滨屹滨互联网科技有限公司 佛山市拉法互联网科技有限公司 元气玛特互联网科技公司是外包吗 35岁以上互联网科技行业 南京一网一红互联网科技有限公司 易企秀互联网科技是做什么的 上海旺乐尼互联网科技有限公司 互联网科技产业 腾讯阿里互联网科技 善班互联网科技 中国互联网金融科技基金 蓝色互联网科技 互联网汽车科技小镇 互联网科技商业 互联网在线教育科技创业 山东蛋芒互联网科技有限公司 陕西彩京云互联网科技有限公司
0