SHOW GLOBAL STATUS 源码实现原理是什么?

摘要:问题 在 MySQL 中,查询全局状态变量的方式一般有两种:SHOW GLOBAL STATUS和performance_schema.global_status。 但不知道大家注意到没有,performance_schema.global
问题 在 MySQL 中,查询全局状态变量的方式一般有两种:SHOW GLOBAL STATUS和performance_schema.global_status。 但不知道大家注意到没有,performance_schema.global_status 返回的状态变量数要远远少于 SHOW GLOBAL STATUS 。 具体来说, 在 MySQL 8.4.2 中,SHOW GLOBAL STATUS 返回了 503 个变量,而 performance_schema.global_status 只返回了 336 个。 在 MySQL 5.7.44 中,SHOW GLOBAL STATUS 返回了 354 个变量,而 performance_schema.global_status 只返回了 207 个。 有的童鞋可能会认为这两者的实现方式不一样,但事实上,从 MySQL 5.7 开始,当执行 SHOW GLOBAL STATUS 时,MySQL 并不是直接从内存中的状态变量获取数据,而是通过查询 performance_schema.global_status 表来间接获取。 既然两者的实现方式是一样的,为什么返回的变量数会不一样? 带着这个问题,接下来我们具体分析下 SHOW GLOBAL STATUS 的实现原理。本文主要包括以下几个部分: 状态变量是在哪里定义的? 状态变量值的来源。 SHOW GLOBAL STATUS 的实现原理。 performance_schema.global_status 的实现原理。 为什么 performance_schema.global_status 返回的变量数比 SHOW GLOBAL STATUS 少。 状态变量是在哪里定义的? 状态变量的来源主要有三个: Server 层面的状态变量:这些状态变量主要在status_vars(mysqld.cc)中定义。在 MySQL 8.4 中,共有 321 个状态变量。其中包括了 com_status_vars 中定义的 167 个 Com 相关的变量。 插件中的状态变量: InnoDB:在innodb_status_variables(ha_innodb.cc)中定义 ,共 76 个。 半同步复制:在semi_sync_master_status_vars(semisync_source_plugin.cc)中定义,共 14 个,从库只有 1 个。 组复制:在group_replication_status_vars(plugin.cc)中定义,共 22 个。 performance_schema:在pfs_status_vars(ha_perfschema.cc)中定义,共 33 个。 mysqlx:在m_plugin_status_variables(status_variables.cc)中定义,共 78 个。 Component 中的状态变量:例如,在密码认证插件 validate_password 中定义的状态变量。 这些变量会通过add_status_vars函数添加到一个数组中 all_status_vars。注意这个数组名,后面讲解原理时会用到。 //sql/mysqld.cc intinit_common_variables(){ ... if(add_status_vars(status_vars))return1; ... } //sql/sql_plugin.cc staticintplugin_initialize(st_plugin_int*plugin){ ... if(plugin->plugin->status_vars){ if(add_status_vars(plugin->plugin->status_vars))gotoerr; } ... } //sql/server_component/component_status_var_service.cc DEFINE_BOOL_METHOD(mysql_status_variable_registration_imp::register_variable, (SHOW_VAR*status_var)){ try{ if(add_status_vars(status_var))returntrue; returnfalse; }catch(...){ mysql_components_handle_std_exception(__func__); } returntrue; } //sql/sql_show.cc booladd_status_vars(constSHOW_VAR*list){ ... while(list->name)all_status_vars.push_back(*list++); ... } 状态变量值的来源 状态变量的值是在定义时指定的,以 Server 层面的状态变量为例: SHOW_VARstatus_vars[]={ {"Aborted_clients",(char*)&aborted_threads,SHOW_LONG,SHOW_SCOPE_GLOBAL}, ... {"Bytes_received",(char*)offsetof(System_status_var,bytes_received), SHOW_LONGLONG_STATUS,SHOW_SCOPE_ALL}, ... {"Com",(char*)com_status_vars,SHOW_ARRAY,SHOW_SCOPE_ALL}, ... {"Uptime",(char*)&show_starttime,SHOW_FUNC,SHOW_SCOPE_GLOBAL}, ... }; status_vars 是一个数组,其元素类型是SHOW_VAR,每个元素代表一个状态变量。每个元素包含四个字段,依次是:变量名、变量值、变量类型和变量作用范围。所以,通过元素的第二个字段,就可以确定该状态变量值的来源。 上面列举了四个有代表性的状态变量: Aborted_clients:变量值来源于全局变量 aborted_threads(extern ulong aborted_threads)。 Bytes_received:变量值来自于 System_status_var 结构体中 bytes_received 字段的内存偏移量(offsetof(System_status_var, bytes_received))。 System_status_var 常用于以下场景: global_status_var:用于存储全局的状态变量。连接断开后,会通过add_to_status函数将对应线程的状态变量添加到 global_status_var 中。 status_var:用于存储每个线程的状态变量。 query_start_status:保存上一个操作结束时线程的状态变量,只在log_slow_extra为 ON 时使用。 Com:变量值来自于com_status_vars数组(怎么知道它是一个数组呢?实际上看的是第三个字段,SHOW_ARRAY 代表它是一个数组),该数组定义了 Com 相关的状态变量。 Uptime:变量值由show_starttime函数(SHOW_FUNC 代表它是一个函数)生成。下面是该函数的具体实现。 staticintshow_starttime(THD*thd,SHOW_VAR*var,char*buff){ var->type=SHOW_LONGLONG; var->value=buff; *((longlong*)buff)= (longlong)(thd->query_start_in_secs()-server_start_time); return0; } 不难看出,Uptime 实际上是通过查询的开始时间(thd->set_time()中设置的)减去 MySQL 服务器的启动时间得到的。 SHOW GLOBAL STATUS 的实现原理 当我们执行 SHOW GLOBAL STATUS 时,实际上查询的是 performance_schema.global_status。 这一转化操作是在build_show_global_status函数中实现的。该函数会将表名(table_name,即 global_status 表)、LIKE 子句(wild)和 WHERE 子句(where_cond)传递给build_query函数,后者会构造对应的 SQL 查询解析树。 //sql/sql_show_status.cc Query_block*build_show_global_status(constPOS&pos,THD*thd, constString*wild,Item*where_cond){ staticconstLEX_CSTRINGtable_name={STRING_WITH_LEN("global_status")}; returnbuild_query(pos,thd,SQLCOM_SHOW_STATUS,table_name,wild, where_cond); } 在不指定任何查询条件的情况下,SHOW GLOBAL STATUS 对应的查询语句如下: SELECT*FROM (SELECTVARIABLE_NAMEasVariable_name,VARIABLE_VALUEasValue FROMperformance_schema.global_status)global_status performance_schema.global_status 的实现原理 查询 performance_schema.global_status 时,MySQL 会通过调用MaterializeIterator<Profiler>::MaterializeOperand函数实现数据的物化(即构造查询结果),除此之外,这个函数还会逐行读取数据并将其写入目标表。 下面是该函数简化后的代码。 boolMaterializeIterator<Profiler>::MaterializeOperand(constOperand&operand, ha_rows*stored_rows){ ... if(operand.subquery_iterator->Init()){ returntrue; } PFSBatchModepfs_batch_mode(operand.subquery_iterator.get()); while(true){ ... interror=read_next_row(operand); ... error=t->file->ha_write_row(t->record[0]); ... returnfalse; } 具体来说, operand.subquery_iterator->Init()会实现数据的物化(即构造查询结果)。 read_next_row(operand)会逐行读取数据,并将数据写到 table->record[0] 中,table->record[0] 是当前行的数据缓冲区。 t->file->ha_write_row(t->record[0])会将 table->record[0] 中的数据写到 performance_schema.global_status 中。 接下来,我们具体分析下构造查询结果和数据读取这两个步骤的实现逻辑。 构造查询结果 下面是operand.subquery_iterator->Init()调用后的堆栈信息。 #0PFS_status_variable_cache::do_materialize_global()at/usr/src/mysql-8.4.2/storage/perfschema/pfs_variable.cc:1178 #1PFS_variable_cache<Status_variable>::materialize_global()at/usr/src/mysql-8.4.2/storage/perfschema/pfs_variable.h:526 #2table_global_status::rnd_init()at/usr/src/mysql-8.4.2/storage/perfschema/table_global_status.cc:123 #3ha_perfschema::rnd_init()at/usr/src/mysql-8.4.2/storage/perfschema/ha_perfschema.cc:1733 #4handler::ha_rnd_init()at/usr/src/mysql-8.4.2/sql/handler.cc:2961 #5TableScanIterator::Init()at/usr/src/mysql-8.4.2/sql/iterators/basic_row_iterators.cc:260 #6MaterializeIterator::MaterializeOperand()at/usr/src/mysql-8.4.2/sql/iterators/composite_iterators.cc:2759 查询结果的构造主要是在PFS_status_variable_cache::do_materialize_global()函数中实现的。 下面我们看看这个函数的具体实现细节。 intPFS_status_variable_cache::do_materialize_global(){ //这个变量用来汇总全局的状态变量(global_status_var)和当前所有线程的状态变量(status_var) System_status_varstatus_totals; ... if(!m_external_init){ //基于all_status_vars构建一个满足条件的状态变量数组m_show_var_array init_show_var_array(OPT_GLOBAL,true); } //初始化PFS_connection_status_visitor,将status_vars赋值给m_status_vars PFS_connection_status_visitorvisitor(&status_totals); //这个函数非常关键,它会将全局的状态变量(global_status_var)和当前所有线程的状态变量(status_var)累加汇总到 m_status_vars 中。 PFS_connection_iterator::visit_global(false,/*hosts*/ false,/*users*/ false,/*accounts*/ false,/*threads*/ true,/*THDs*/ &visitor); //这个函数也非常关键,它会遍历 m_show_var_array 中的状态变量,获取其值并进行格式化处理,最终将处理后的结果缓存到 m_cache 中。 manifest(m_current_thd,m_show_var_array.begin(),&status_totals,"",false, true); ... return0; } 该函数的处理流程如下: 基于 all_status_vars 构建一个满足条件的状态变量数组 m_show_var_array。至于需要满足什么条件,后面会详细说明。 将全局的状态变量(global_status_var)和当前所有线程的状态变量(status_var)累加汇总到 status_totals 中。 遍历 m_show_var_array 中的状态变量,根据变量的类型(如 SHOW_FUNC、SHOW_ARRAY 等)进行不同的处理,并将处理后的状态变量存储到 m_cache 缓存中。具体处理逻辑如下: 对于 SHOW_FUNC 类型的变量,manifest会递归执行函数来计算变量的最终值。 对于 SHOW_ARRAY 类型的变量,函数会递归调用manifest,以展开数组中的每一个状态变量。 状态变量添加到 m_cache 之前,会先转换为 Status_variable 类型。 读取数据 下面是read_next_row(operand)调用后的堆栈信息。 #0PFS_variable_cache<Status_variable>::get()at/usr/src/mysql-8.4.2/storage/perfschema/pfs_variable.h:382 #1table_global_status::rnd_next()at/usr/src/mysql-8.4.2/storage/perfschema/table_global_status.cc:131 #2ha_perfschema::rnd_next()at/usr/src/mysql-8.4.2/storage/perfschema/ha_perfschema.cc:1757 #3handler::ha_rnd_next()at/usr/src/mysql-8.4.2/sql/handler.cc:3006 #4TableScanIterator::Read()at/usr/src/mysql-8.4.2/sql/iterators/basic_row_iterators.cc:278 #5MaterializeIterator::read_next_row()at/usr/src/mysql-8.4.2/sql/iterators/composite_iterators.cc:2278 #6MaterializeIterator::MaterializeOperand()at/usr/src/mysql-8.4.2/sql/iterators/composite_iterators.cc:2771 read_next_row(operand)最后会调用PFS_variable_cache<Status_variable>::get(),而这个函数实际上读取的就是 m_cache 中的元素。 //storage/perfschema/pfs_variable.h:382 constVar_type*get(uintindex=0)const{ if(index>=m_cache.size()){ returnnullptr; } constVar_type*p=&m_cache.at(index); returnp; } 为什么 performance_schema.global_status 返回的变量数比 SHOW GLOBAL STATUS 少? 前面我们提到过,在构造查询结果时,会先基于 all_status_vars 构建一个满足条件的状态变量数组 m_show_var_array。 具体需要满足什么条件,是在PFS_status_variable_cache::filter_show_var函数中定义的。 boolPFS_status_variable_cache::filter_show_var(constSHOW_VAR*show_var, boolstrict){ if(!match_scope(show_var->scope,strict)){ returntrue; } if(filter_by_name(show_var)){ returntrue; } if(m_aggregate&&!can_aggregate(show_var->type)){ returntrue; } returnfalse; } boolPFS_status_variable_cache::filter_by_name(constSHOW_VAR*show_var){ assert(show_var); assert(show_var->name); if(show_var->type==SHOW_ARRAY){ /*TheSHOW_ARRAYnameistheprefixforthevariablesinthesubarray.*/ constchar*prefix=show_var->name; /*ExcludeCOMcountersifnotaSHOWSTATUScommand.*/ if(!my_strcasecmp(system_charset_info,prefix,"Com")&&!m_show_command){ returntrue; } } returnfalse; } 从代码中可以看到,需要判断的条件有三个: 变量作用范围:因为init_show_var_array(OPT_GLOBAL, true)中指定了 OPT_GLOBAL,所以这里会过滤掉变量作用范围为 SHOW_SCOPE_SESSION 的状态变量。在 Server 层面的状态变量中,这样的变量有 6 个:Compression、Compression_algorithm、Compression_level、Last_query_cost、Last_query_partial_plans、Tls_sni_server_name。 对于非 m_show_command 类的查询(其实就是指的是直接查询 performance_schema.global_status 这种方式),还会剔除com_status_vars数组中 Com 相关的状态变量。这也就是为什么 performance_schema.global_status 返回的变量数比 SHOW GLOBAL STATUS 中少。 查询聚合数据:如果查询的是 status_by_account、status_by_host 或 status_by_user 之类的聚合表,还会剔除无法聚合的状态变量。 总结 状态变量的来源主要有三个:Server、插件和 Component。 如果想查看某个状态变量值的来源,直接查看定义部分对应元素的第二个字段即可。 当我们执行 SHOW GLOBAL STATUS 时,实际上查询的是 performance_schema.global_status。 performance_schema.global_status 在实现上主要分为两步:1. 构造查询结果,将所有变量的值存储到一个缓存(m_cache)中;2. 数据读取,直接从缓存中读取变量值。 之所以 performance_schema.global_status 返回的变量数比 SHOW GLOBAL STATUS 中少,主要是PFS_status_variable_cache::filter_by_name中的限制。 需要注意的是,如果查询中指定了过滤条件,过滤操作会发生在数据读取阶段,而不是查询结果构造阶段。