基于源码分析 SHOW GLOBAL STATUS 的实现原理
问题
在 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
int init_common_variables() {
...
if (add_status_vars(status_vars)) return 1;
...
}
// sql/sql_plugin.cc
static int plugin_initialize(st_plugin_int *plugin) {
...
if (plugin->plugin->status_vars) {
if (add_status_vars(plugin->plugin->status_vars)) goto err;
}
...
}
// 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)) return true;
return false;
} catch (...) {
mysql_components_handle_std_exception(__func__);
}
return true;
}
// sql/sql_show.cc
bool add_status_vars(const SHOW_VAR *list) {
...
while (list->name) all_status_vars.push_back(*list++);
...
}
状态变量值的来源
状态变量的值是在定义时指定的,以 Server 层面的状态变量为例:
SHOW_VAR status_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 代表它是一个函数)生成。下面是该函数的具体实现。
static int show_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);
return 0;
}
不难看出,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(const POS &pos, THD *thd,
const String *wild, Item *where_cond) {
static const LEX_CSTRING table_name = {STRING_WITH_LEN("global_status")};
return build_query(pos, thd, SQLCOM_SHOW_STATUS, table_name, wild,
where_cond);
}
在不指定任何查询条件的情况下,SHOW GLOBAL STATUS 对应的查询语句如下:
SELECT * FROM
(SELECT VARIABLE_NAME as Variable_name, VARIABLE_VALUE as Value
FROM performance_schema.global_status) global_status
performance_schema.global_status 的实现原理
查询 performance_schema.global_status 时,MySQL 会通过调用MaterializeIterator<Profiler>::MaterializeOperand函数实现数据的物化(即构造查询结果),除此之外,这个函数还会逐行读取数据并将其写入目标表。
下面是该函数简化后的代码。
bool MaterializeIterator<Profiler>::MaterializeOperand(const Operand &operand,
ha_rows *stored_rows) {
...
if (operand.subquery_iterator->Init()) {
return true;
}
PFSBatchMode pfs_batch_mode(operand.subquery_iterator.get());
while (true) {
...
int error = read_next_row(operand);
...
error = t->file->ha_write_row(t->record[0]);
...
return false;
}
具体来说,
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()调用后的堆栈信息。
#0 PFS_status_variable_cache::do_materialize_global() at /usr/src/mysql-8.4.2/storage/perfschema/pfs_variable.cc:1178
#1 PFS_variable_cache<Status_variable>::materialize_global() at /usr/src/mysql-8.4.2/storage/perfschema/pfs_variable.h:526
#2 table_global_status::rnd_init() at /usr/src/mysql-8.4.2/storage/perfschema/table_global_status.cc:123
#3 ha_perfschema::rnd_init() at /usr/src/mysql-8.4.2/storage/perfschema/ha_perfschema.cc:1733
#4 handler::ha_rnd_init() at /usr/src/mysql-8.4.2/sql/handler.cc:2961
#5 TableScanIterator::Init() at /usr/src/mysql-8.4.2/sql/iterators/basic_row_iterators.cc:260
#6 MaterializeIterator::MaterializeOperand() at /usr/src/mysql-8.4.2/sql/iterators/composite_iterators.cc:2759
查询结果的构造主要是在PFS_status_variable_cache::do_materialize_global()函数中实现的。
下面我们看看这个函数的具体实现细节。
int PFS_status_variable_cache::do_materialize_global() {
// 这个变量用来汇总全局的状态变量(global_status_var)和当前所有线程的状态变量(status_var)
System_status_var status_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_visitor visitor(&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);
...
return 0;
}
该函数的处理流程如下:
- 基于 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)调用后的堆栈信息。
#0 PFS_variable_cache<Status_variable>::get() at /usr/src/mysql-8.4.2/storage/perfschema/pfs_variable.h:382
#1 table_global_status::rnd_next() at /usr/src/mysql-8.4.2/storage/perfschema/table_global_status.cc:131
#2 ha_perfschema::rnd_next() at /usr/src/mysql-8.4.2/storage/perfschema/ha_perfschema.cc:1757
#3 handler::ha_rnd_next() at /usr/src/mysql-8.4.2/sql/handler.cc:3006
#4 TableScanIterator::Read() at /usr/src/mysql-8.4.2/sql/iterators/basic_row_iterators.cc:278
#5 MaterializeIterator::read_next_row() at /usr/src/mysql-8.4.2/sql/iterators/composite_iterators.cc:2278
#6 MaterializeIterator::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
const Var_type *get(uint index = 0) const {
if (index >= m_cache.size()) {
return nullptr;
}
const Var_type *p = &m_cache.at(index);
return p;
}
为什么 performance_schema.global_status 返回的变量数比 SHOW GLOBAL STATUS 少?
前面我们提到过,在构造查询结果时,会先基于 all_status_vars 构建一个满足条件的状态变量数组 m_show_var_array。
具体需要满足什么条件,是在PFS_status_variable_cache::filter_show_var函数中定义的。
bool PFS_status_variable_cache::filter_show_var(const SHOW_VAR *show_var,
bool strict) {
if (!match_scope(show_var->scope, strict)) {
return true;
}
if (filter_by_name(show_var)) {
return true;
}
if (m_aggregate && !can_aggregate(show_var->type)) {
return true;
}
return false;
}
bool PFS_status_variable_cache::filter_by_name(const SHOW_VAR *show_var) {
assert(show_var);
assert(show_var->name);
if (show_var->type == SHOW_ARRAY) {
/* The SHOW_ARRAY name is the prefix for the variables in the sub array. */
const char *prefix = show_var->name;
/* Exclude COM counters if not a SHOW STATUS command. */
if (!my_strcasecmp(system_charset_info, prefix, "Com") && !m_show_command) {
return true;
}
}
return false;
}
从代码中可以看到,需要判断的条件有三个:
- 变量作用范围:因为
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中的限制。 - 需要注意的是,如果查询中指定了过滤条件,过滤操作会发生在数据读取阶段,而不是查询结果构造阶段。
基于源码分析 SHOW GLOBAL STATUS 的实现原理的更多相关文章
- 从壹开始微服务 [ DDD ] 之十一 ║ 基于源码分析,命令分发的过程(二)
缘起 哈喽小伙伴周三好,老张又来啦,DDD领域驱动设计的第二个D也快说完了,下一个系列我也在考虑之中,是 Id4 还是 Dockers 还没有想好,甚至昨天我还想,下一步是不是可以写一个简单的Angu ...
- Spring Ioc源码分析系列--@Autowired注解的实现原理
Spring Ioc源码分析系列--@Autowired注解的实现原理 前言 前面系列文章分析了一把Spring Ioc的源码,是不是云里雾里,感觉并没有跟实际开发搭上半毛钱关系?看了一遍下来,对我的 ...
- Java容器 | 基于源码分析List集合体系
一.容器之List集合 List集合体系应该是日常开发中最常用的API,而且通常是作为面试压轴问题(JVM.集合.并发),集合这块代码的整体设计也是融合很多编程思想,对于程序员来说具有很高的参考和借鉴 ...
- zuul源码分析-探究原生zuul的工作原理
前提 最近在项目中使用了SpringCloud,基于zuul搭建了一个提供加解密.鉴权等功能的网关服务.鉴于之前没怎么使用过Zuul,于是顺便仔细阅读了它的源码.实际上,zuul原来提供的功能是很单一 ...
- Mybatis源码分析--关联表查询及延迟加载原理(二)
在上一篇博客Mybatis源码分析--关联表查询及延迟加载(一)中我们简单介绍了Mybatis的延迟加载的编程,接下来我们通过分析源码来分析一下Mybatis延迟加载的实现原理. 其实简单来说Myba ...
- RocketMQ源码分析之RocketMQ事务消息实现原理上篇(二阶段提交)
在阅读本文前,若您对RocketMQ技术感兴趣,请加入 RocketMQ技术交流群 根据上文的描述,发送事务消息的入口为: TransactionMQProducer#sendMessageInTra ...
- MyBatis源码分析(各组件关系+底层原理
MyBatis源码分析MyBatis流程图 下面将结合代码具体分析. MyBatis具体代码分析 SqlSessionFactoryBuilder根据XML文件流,或者Configuration类实例 ...
- Flask源码分析二:路由内部实现原理
前言 Flask是目前为止我最喜欢的一个Python Web框架了,为了更好的掌握其内部实现机制,这两天准备学习下Flask的源码,将由浅入深跟大家分享下,其中Flask版本为1.1.1. 上次了解了 ...
- Vue.js 源码分析(四) 基础篇 响应式原理 data属性
官网对data属性的介绍如下: 意思就是:data保存着Vue实例里用到的数据,Vue会修改data里的每个属性的访问控制器属性,当访问每个属性时会访问对应的get方法,修改属性时会执行对应的set方 ...
- 源码分析 Alibaba sentinel 滑动窗口实现原理(文末附原理图)
要实现限流.熔断等功能,首先要解决的问题是如何实时采集服务(资源)调用信息.例如将某一个接口设置的限流阔值 1W/tps,那首先如何判断当前的 TPS 是多少?Alibaba Sentinel 采用滑 ...
随机推荐
- 经验总结之 _DEBUGGER _03 _Server Tomcat v8.0 Server at localhost was unable to start within xx seconds
经验总结之 _DEBUGGER _03 _Server Tomcat v8.0 Server at localhost was unable to start within xx seconds 好好 ...
- Python学习第一周记录
网上下载了python视频,找了相关资料,看了一个星期,先总结下这个礼拜的小知识点: 第一个小程序"hello word" 开始了解变量赋值,引用输入input; 学会使用判断if ...
- 基于微服务SDK框架与JavaAgent技术,低成本助力应用高效发布
本文分享自<华为云DTSE>第五期开源专刊,作者:聂子雄 华为云高级工程师.李来 华为云高级工程师. 微服务是一种用于构建应用的架构方案,可使应用的各个部分既能独立工作,又能协同配合,微服 ...
- Java学习十七—反射机制:解锁代码的无限可能
Java学习十七-反射机制:解锁代码的无限可能 一.关于反射 1.1 简介 Java 反射(Reflection)是Java 的特征之一,它允许程序在运行时动态地访问和操作类的信息,包括类的属性.方法 ...
- 研发LLM模型,如何用数值表示人类自然语言?
上一篇:<人工智能--自然语言处理简介> 序言:人工智能大语言模型(LLM)如何理解人类的自然语言?这个过程的核心在于将文本转化为计算机能处理的数值形式,经过计算,最终达到对语言的理解.起 ...
- Go中数组和切片
数组和切片 [1].数组 1.什么是数组 一组数 数组需要是相同类型的数据的集合 数组是需要定义大小的 数组一旦定义了大小是不可以改变的. package main import "fmt& ...
- 读书笔记-C#8.0本质论-03
15. 委托和lambda表达式 15.1 委托概述 namespace ConsoleApp1; internal static class Program { private enum SortT ...
- Threejs入门-灯光
在 Three.js 中,灯光是非常重要的元素之一,它能够模拟现实世界中的光照效果,帮助我们打造更加真实的三维场景.灯光的种类和配置方式可以影响整个场景的视觉效果,在不同的应用中,灯光的使用非常关键. ...
- 如何使用blender生成城市群
在我们做数字孪生相关的项目的时候,会需要生成一些城市的模型,这时候我们可以使用 blender 来生成一些城市的模型. 我们,先来看一下效果. 安装 blender blender 是一个开源的 3D ...
- 【论文系列】PPO知识点梳理 (尽我可能细致通俗理解!)
零.题记 这篇博客一方面为了记录当前的知识点,另一方面PPO算法实在是太重要了,不但要从理论上理解它到底是怎样实现的,还需要从代码方面进行学习和记录,这里我就通俗的将这个知识点进行简单的记录,用来日后 ...