深入理解MySQL 5.7 GTID系列(五) gtid_executed&gtid_purged什么时候更新

509 阅读14分钟

本节将集中讨论下面三种GTID更新的时机,这部分相当重要,后面的故障案列会和这节有关。下面先来看一下他们的定义: mysql.gtid_executed表:GTID持久化的介质,MySQL启动阶段会读取这个表来获取gtid_executed变量的值。

本节将集中讨论下面三种

更新的时机,这部分相当重要,后面的故障案列会和这节有关。下面先来看一下他们的定义:

这也是我们DBA通常能够观察到的几种

,有了前文的描述我们知道其中表是一种持久化的介质,而变量和变量则对应了,中的内存数据。他们分别表示数据库执行了哪些事务,有哪些事务由于文件的删除已经丢失了。
其次我们先来达成一个共识
变量一定是实时更新的不管主库和从库。我们的讨论分为主库,从库和通用从源码的角度进行详细讨论。并且约定都是打开的情况下。最后给出最终总结。

一、主库修改时机

(1)

关闭

不生成

变量变量均不更新。

(2)

打开

发生切换的时候保存直到上一个文件执行过的全部
GTID
,它不是实时更新的。

栈帧如下:

#0 Gtid_table_persistor::save (this=0x2f9f9c0, gtid_set=0x7ffff03595a0) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/rpl_gtid_persist.cc:425
#1 0x0000000001803dbe in Gtid_state::save (this=0x2ff8bb0, gtid_set=0x7ffff03595a0) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/rpl_gtid_state.cc:796
#2 0x0000000001803f62 in Gtid_state::save_gtids_of_last_binlog_into_table (this=0x2ff8bb0, on_rotation=true)
 at /root/mysql5.7.14/percona-server-5.7.14-7/sql/rpl_gtid_state.cc:835
#3 0x000000000185266d in MYSQL_BIN_LOG::new_file_impl (this=0x2dffc80, need_lock_log=false, extra_description_event=0x0)
 at /root/mysql5.7.14/percona-server-5.7.14-7/sql/binlog.cc:6751
#4 0x00000000018520a7 in MYSQL_BIN_LOG::new_file_without_locking (this=0x2dffc80, extra_description_event=0x0)
 at /root/mysql5.7.14/percona-server-5.7.14-7/sql/binlog.cc:6636
#5 0x0000000001853e67 in MYSQL_BIN_LOG::rotate (this=0x2dffc80, force_rotate=true, check_purge=0x7ffff0359c4b)
 at /root/mysql5.7.14/percona-server-5.7.14-7/sql/binlog.cc:7292

其主要逻辑在

中我们在随后的部分讨论这个函数逻辑。

如前文所述

阶段生成,在阶段才计入变量,它是实时更新的。

栈帧如下:

#0 Gtid_set::_add_gtid (this=0x2ff8d38, sidno=1, gno=16) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/rpl_gtid.h:1135
#1 0x0000000001804576 in Gtid_set::_add_gtid (this=0x2ff8d38, gtid=...) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/rpl_gtid.h:1166
#2 0x00000000018024ba in Gtid_state::update_gtids_impl (this=0x2ff8bb0, thd=0x7fff2c000b70, is_commit=true)
 at /root/mysql5.7.14/percona-server-5.7.14-7/sql/rpl_gtid_state.cc:304
#3 0x00000000018020df in Gtid_state::update_on_commit (this=0x2ff8bb0, thd=0x7fff2c000b70) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/rpl_gtid_state.cc:148
#4 0x00000000018573d4 in MYSQL_BIN_LOG::process_commit_stage_queue (this=0x2dffc80, thd=0x7fff2c000b70, first=0x7fff2c000b70)
 at /root/mysql5.7.14/percona-server-5.7.14-7/sql/binlog.cc:8646
#5 0x0000000001858b51 in MYSQL_BIN_LOG::ordered_commit (this=0x2dffc80, thd=0x7fff2c000b70, all=false, skip_commit=false)
 at /root/mysql5.7.14/percona-server-5.7.14-7/sql/binlog.cc:9304

其主要逻辑在

中我们在随后的部分讨论这个函数逻辑。

触发的清理的情况下,比如或者超过参数设置的天数后自动删除,需要将丢失的计入这个变量中。
栈帧如下:

#0 MYSQL_BIN_LOG::init_gtid_sets (this=0x2e00280, all_gtids=0x0, lost_gtids=0x2fcaee8, verify_checksum=false, need_lock=false, trx_parser=0x0, gtid_partial_trx=0x0, is_server_starting=false)
 at /root/mysql5.7.14/percona-server-5.7.14-7/sql/binlog.cc:4333
#1 0x0000000001850b8e in MYSQL_BIN_LOG::purge_logs (this=0x2e00280, to_log=0x7fff57a74ad0 "/root/mysql5.7.14/percona-server-5.7.14-7/mysql-test/var/mysqld.1/test.000202", included=false, need_lock_index=true, 
 need_update_threads=true, decrease_log_space=0x0, auto_purge=false) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/binlog.cc:6036
#2 0x0000000001848ecf in purge_master_logs (thd=0x7fff49200dc0, to_log=0x7fff492051a8 "test.000202") at /root/mysql5.7.14/percona-server-5.7.14-7/sql/binlog.cc:2815

其主要逻辑在

中,我们随后查看其代码片段,同时函数是一个及其重要的函数,主要用在:

随后我会单独一节来讲解

模块的初始化还会讲解这个函数。

二、主库修改时机源码函数分析

这里就对上面提到的主要逻辑函数进行分析

logged_gtids_last_binlog.add_interval_memory(PREALLOCATED_INTERVAL_COUNT, iv); //这里构建一个logged_gtids_last_binlog集合来保存切换后需要写入表和previous_gtids_logged的Gtid
 /*
 logged_gtids_last_binlog= executed_gtids - previous_gtids_logged -
 gtids_only_in_table
 */
 global_sid_lock->wrlock();//
 ret= (logged_gtids_last_binlog.add_gtid_set(&executed_gtids) != //将当前执行过的Gtid全部加入logged_gtids_last_binlog 列如:executed_gtids start=1, end=27 
 RETURN_STATUS_OK);
 if (!ret)
 {
 logged_gtids_last_binlog.remove_gtid_set(&previous_gtids_logged); //获得上一个binlog文件包含的全部Gtid,并且做一个差集 列如:previous_gtids_logged 为start=1, end=25
 //做完差集后logged_gtids_last_binlog为start=26, end=27
 logged_gtids_last_binlog.remove_gtid_set(>ids_only_in_table);//此处主库一定为空,除非异常情况
 if (!logged_gtids_last_binlog.is_empty()) 
 {
 /* Prepare previous_gtids_logged for next binlog on binlog rotation */
 if (on_rotation)
 ret= previous_gtids_logged.add_gtid_set(&logged_gtids_last_binlog);//将这个start=26, end=27的Gtid集合加入到previous_gtids_logged中,这样previous_gtids_logged也完整了
 global_sid_lock->unlock();
 /* Save set of GTIDs of the last binlog into gtid_executed table */
 if (!ret)
 ret= save(&logged_gtids_last_binlog);//将这个start=26, end=27的Gtid集合写入到表mysql.gtid_executed表中
 }

while (g.sidno != 0)
 {
 if (g.sidno != prev_sidno)
 sid_locks.lock(g.sidno);
 owned_gtids.remove_gtid(g); //从owned_gtid中去掉
 git.next();
 g= git.get();
 if (is_commit)
 executed_gtids._add_gtid(g);//将这个Gtid加入到executed_gtids
 }

if (!is_relay_log)
 {
 global_sid_lock->wrlock();
 error= init_gtid_sets(NULL,
 const_cast<Gtid_set *>(gtid_state->get_lost_gtids()),
 opt_master_verify_checksum,
 false/*false=don't need lock*/,
 NULL/*trx_parser*/, NULL/*gtid_partial_trx*/);//这里我看到将gtid_state->lost_gtids直接传入给了init_gtid_sets
 //init_gtid_sets会做正向查找获得gtid_state->lost_gtids这个函数稍后
 //详细讨论
 global_sid_lock->unlock();
 if (error)
 goto err;
 }

三、从库修改时机

(1)

关闭或者开启参数关闭的情况

将他们放到一起因为他们的表现完全一样。

前面已经说过这种情况下从库没有办法通过

来持久化执行过的事务,只能通过实时更新表来保存,所以必须要要实时将
GTID
持久化到
mysql.gtid_executed
表中。实际上实时保存
发生在阶段之前,也就是最开始。但是对于主库来讲由于还没有生成,那么则不能写入

栈帧如下:

#0 commit_owned_gtids (thd=0x7fffec000970, all=false, need_clear_owned_gtid_ptr=0x7ffff08bf2cb) at /mysql/mysql-5.7.17/sql/handler.cc:1571
#1 0x0000000000f465c3 in ha_commit_trans (thd=0x7fffec000970, all=false, ignore_global_read_lock=false) at /mysql/mysql-5.7.17/sql/handler.cc:1669
#2 0x0000000001686f0d in trans_commit_stmt (thd=0x7fffec000970) at /mysql/mysql-5.7.17/sql/transaction.cc:458
#3 0x000000000180fb16 in rows_event_stmt_cleanup (rli=0x3937d30, thd=0x7fffec000970) at /mysql/mysql-5.7.17/sql/log_event.cc:11124
#4 0x000000000180f801 in Rows_log_event::do_apply_event (this=0x7fffec018020, rli=0x3937d30) at /mysql/mysql-5.7.17/sql/log_event.cc:11026
#5 0x00000000017f7b7b in Log_event::apply_event (this=0x7fffec018020, rli=0x3937d30) at /mysql/mysql-5.7.17/sql/log_event.cc:3324
#6 0x00000000018690a6 in apply_event_and_update_pos (ptr_ev=0x7ffff08bf830, thd=0x7fffec000970, rli=0x3937d30) at /mysql/mysql-5.7.17/sql/rpl_slave.cc:4702
#7 0x000000000186a75e in exec_relay_log_event (thd=0x7fffec000970, rli=0x3937d30) at /mysql/mysql-5.7.17/sql/rpl_slave.cc:5212
#8 0x0000000001870d07 in handle_slave_sql (arg=0x38cc1c0) at /mysql/mysql-5.7.17/sql/rpl_slave.cc:7320

其主要逻辑包含在

中。

这个和主库一样实时更新,不做讨论。

由于压根没有binlog来记录已经执行过的Gtid事务,所以gtid_purged变量实时更新
其更改处于整个

的结尾如下:

if (need_clear_owned_gtid)
 {
 thd->server_status&= ~SERVER_STATUS_IN_TRANS;
 /*
 Release the owned GTID when binlog is disabled, or binlog is
 enabled and log_slave_updates is disabled with slave SQL thread
 or slave worker thread.
 */
 if (error)
 gtid_state->update_on_rollback(thd);
 else
 gtid_state->update_on_commit(thd);
 }

栈帧如下(这个栈帧取值5.7.17):

#0 Gtid_set::_add_gtid (this=0x2f9ef40, sidno=2, gno=33083) at /mysql/mysql-5.7.17/sql/rpl_gtid.h:1136
#1 0x00000000017e9990 in Gtid_set::_add_gtid (this=0x2f9ef40, gtid=...) at /mysql/mysql-5.7.17/sql/rpl_gtid.h:1167
#2 0x00000000017e906e in Gtid_state::update_gtids_impl_own_gtid (this=0x2f9eca0, thd=0x7fffec000970, is_commit=true) at /mysql/mysql-5.7.17/sql/rpl_gtid_state.cc:939
#3 0x00000000017e6f3f in Gtid_state::update_gtids_impl (this=0x2f9eca0, thd=0x7fffec000970, is_commit=true) at /mysql/mysql-5.7.17/sql/rpl_gtid_state.cc:240
#4 0x00000000017e6d25 in Gtid_state::update_on_commit (this=0x2f9eca0, thd=0x7fffec000970) at /mysql/mysql-5.7.17/sql/rpl_gtid_state.cc:201
#5 0x0000000000f46d46 in ha_commit_trans (thd=0x7fffec000970, all=true, ignore_global_read_lock=false) at /mysql/mysql-5.7.17/sql/handler.cc:1846
#6 0x000000000168676b in trans_commit (thd=0x7fffec000970) at /mysql/mysql-5.7.17/sql/transaction.cc:239
#7 0x0000000001802aaa in Xid_log_event::do_commit (this=0x7fffec013c60, thd_arg=0x7fffec000970) at /mysql/mysql-5.7.17/sql/log_event.cc:6824
#8 0x00000000018034d7 in Xid_apply_log_event::do_apply_event (this=0x7fffec013ca8, rli=0x3937d30) at /mysql/mysql-5.7.17/sql/log_event.cc:7049

当然其处理逻辑在

中。

(2)

开启同时参数开启的情况

这种情况

执行过的事务可以通过

进行维护,所以

表和变量不需要实时更新。

和主库一致。及在进行日志切换的时候进行更新,不做讨论

和主库一样实时更新,不做讨论
gtid_purged变量修改时机

和主库一致,

删除时更新,不做讨论

四、从库修改时机源码函数分析

//如果 binlog 没有开启包括(log_bin=0 和 sql_log_bin =0 )或者 开启了binlog 但是slave线程并且slave update 没有开启,都会记录gtid到表
//但是这里要注意一点在主库上如果binlog不开启那么thd->owned_gtid.sidno ==0 因为这个时候Gtid都没有生成,生成阶段为order_commit的commit阶段
 if ((!opt_bin_log || (thd->slave_thread && !opt_log_slave_updates)) && 
 (all || !thd->in_multi_stmt_transaction_mode()) && //all 代表是否是显示begin 事务in_multi_stmt_transaction_mode则相反
 !thd->is_operating_gtid_table_implicitly && //是否是GTID_NEXT方式 flase
 !thd->is_operating_substatement_implicitly)//是否是子语句 flase
 {
 /*
 If the binary log is disabled for this thread (either by
 log_bin=0 or sql_log_bin=0 or by log_slave_updates=0 for a
 slave thread), then the statement will not be written to
 the binary log. In this case, we should save its GTID into
 mysql.gtid_executed table and @@GLOBAL.GTID_EXECUTED as it
 did when binlog is enabled.
 */
 if (thd->owned_gtid.sidno > 0)
 {
 error= gtid_state->save(thd);//就是这里进行了mysql.gtid_executed表的实时更新
 *need_clear_owned_gtid_ptr= true;
 }
 else if (thd->owned_gtid.sidno == THD::OWNED_SIDNO_ANONYMOUS)
 *need_clear_owned_gtid_ptr= true;
 }
if (is_commit)
 {
 DBUG_EXECUTE_IF(
 "rpl_gtid_update_on_commit_simulate_out_of_memory",
 DBUG_SET("+d,rpl_gtid_get_free_interval_simulate_out_of_memory"););
 /*
 Any session adds transaction owned GTID into global executed_gtids.

 If binlog is disabled, we report @@GLOBAL.GTID_PURGED from
 executed_gtids, since @@GLOBAL.GTID_PURGED and @@GLOBAL.GTID_EXECUTED
 are always same, so we did not save gtid into lost_gtids for every
 transaction for improving performance.

 If binlog is enabled and log_slave_updates is disabled, slave
 SQL thread or slave worker thread adds transaction owned GTID
 into global executed_gtids, lost_gtids and gtids_only_in_table.
 */
 executed_gtids._add_gtid(thd->owned_gtid); //加入executed_gtids集合
 thd->rpl_thd_ctx.session_gtids_ctx().
 notify_after_gtid_executed_update(thd);
 if (thd->slave_thread && opt_bin_log && !opt_log_slave_updates)//如果是slave线程同时binlog开启了并且log_slave_updates关闭了
 //如果binlog关闭则使用 executed_gtids这样提高性能前面的注释说了
 {
 lost_gtids._add_gtid(thd->owned_gtid); //写入lost_gtids也就是更新参数gtid_purged变量
 gtids_only_in_table._add_gtid(thd->owned_gtid);
 }
 }
五、通用更改时机
#0 Gtid_table_persistor::delete_all (this=0x2f9f9c0, table=0x7fff2c0116a0) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/rpl_gtid_persist.cc:795
#1 0x000000000180a4ef in Gtid_table_persistor::reset (this=0x2f9f9c0, thd=0x7fff2c000b70) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/rpl_gtid_persist.cc:689
#2 0x0000000001801f2e in Gtid_state::clear (this=0x2ff8bb0, thd=0x7fff2c000b70) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/rpl_gtid_state.cc:36
#3 0x000000000184fee6 in MYSQL_BIN_LOG::reset_logs (this=0x2dffe80, thd=0x7fff2c000b70, delete_only=false)
 at /root/mysql5.7.14/percona-server-5.7.14-7/sql/binlog.cc:5586
#4 0x0000000001872308 in reset_master (thd=0x7fff2c000b70) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/rpl_master.cc:587

其主要逻辑在

中。

栈帧如下:
#0 Gtid_table_persistor::save (this=0x2f9f9c0, gtid_set=0x7ffff0359a70) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/rpl_gtid_persist.cc:425
#1 0x000000000180400a in Gtid_state::save (this=0x2ff8bb0, gtid_set=0x7ffff0359a70) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/rpl_gtid_state.cc:796
#2 0x0000000001803c25 in Gtid_state::add_lost_gtids (this=0x2ff8bb0, gtid_set=0x7ffff0359a70) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/rpl_gtid_state.cc:737
#3 0x00000000016778f3 in Sys_var_gtid_purged::global_update (this=0x2de9fe0, thd=0x7fff2c000b70, var=0x7fff2c006630)
 at /root/mysql5.7.14/percona-server-5.7.14-7/sql/sys_vars.cc:5888
#4 0x00000000014d5cd1 in sys_var::update (this=0x2de9fe0, thd=0x7fff2c000b70, var=0x7fff2c006630) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/set_var.cc:184
#5 0x00000000014d74ee in set_var::update (this=0x7fff2c006630, thd=0x7fff2c000b70) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/set_var.cc:812
#6 0x00000000014d6d1a in sql_set_variables (thd=0x7fff2c000b70, var_list=0x7fff2c003528) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/set_var.cc:669

其主要逻辑在

中。

变量修改时机

在reset master的时候清空本变量
栈帧同上
栈帧同上

变量修改时机

在reset master的时候清空本变量
栈帧同上
栈帧同上

int Gtid_state::clear(THD *thd)
{
 ....
 // the wrlock implies that no other thread can hold any of the mutexes
 sid_lock->assert_some_wrlock();
 lost_gtids.clear();//此处清空gtid_purged变量
 executed_gtids.clear();//此处清空gtid_executed变量
 gtids_only_in_table.clear();//清空only in table Gtid set
 previous_gtids_logged.clear();//清空 previous gtids logged Gtid set
 /* Reset gtid_executed table. */
 if ((ret= gtid_table_persistor->reset(thd)) == 1)//此处清空mysql.gtid_executed表
 {
 /*
 Gtid table is not ready to be used, so failed to
 open it. Ignore the error.
 */
 thd->clear_error();
 ret= 0;
 }
 next_free_gno= 1;
 DBUG_RETURN(ret);
}
enum_return_status Gtid_state::add_lost_gtids(const Gtid_set *gtid_set)
{
 ......
 if (save(gtid_set)) //此处将set gtid_purge的值加入到mysql.gtid_executed表中
 RETURN_REPORTED_ERROR;
 PROPAGATE_REPORTED_ERROR(gtids_only_in_table.add_gtid_set(gtid_set));
 PROPAGATE_REPORTED_ERROR(lost_gtids.add_gtid_set(gtid_set));//此处将set gtid_purge的值加入到gtid_purge变量中
 PROPAGATE_REPORTED_ERROR(executed_gtids.add_gtid_set(gtid_set));//此处将set gtid_purge的值加入到gtid_executed变量中
 lock_sidnos(gtid_set);
 broadcast_sidnos(gtid_set);
 unlock_sidnos(gtid_set);
 DBUG_RETURN(RETURN_STATUS_OK);
}
七、本节小结

为了方便这里将上面的重点文字描述进行提取,去掉源码部分,方便大家阅读

1、主库修改时机

(1)

关闭

不生成

变量变量均不更新。

(2)

打开

BINLOG
发生切换
的时候保存直到上一个
BINLOG
文件执行过的全部
GTID
,它不是实时更新的。
如前文所述阶段生成,在阶段才计入变量,它是实时更新的。
触发的清理的情况下,比如或者超过参数设置的天数后自动删除,需要将丢失的计入这个变量中。

2、从库修改时机

(1)

关闭或者开启参数关闭的情况

实时将持久化到表中。
它是实时更新的。
由于压根没有来记录已经执行过的事务,所以变量实时更新
3、通用修改时机
(1)在
reset master
的时候清空本表。
(2)在
的时候,设置本表。
(1)在的时候清空本变量。
(2)在
的时候,设置本变量。
(3)在
启动的时候初始化设置gtid_executed变量。
(1)在的时候清空本变量。
(2)在
的时候,设置本变量。
(3)在
启动的时候初始化设置gtid_purged变量。

此外

命令除了完成上述功能还会清理,重新初始化从序号1开始。而参数一般只有在reset master后使用,用于搭建从库或者处理从库故障。

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

1、主库和从库对于表,变量,变量
在各种情况下的修改时机
2、
做了什么关于相关的工作
3、
做了什么关于相关的工作

原文链接