基于源码分析 SHOW GLOBAL STATUS 的实现原理

问题

在 MySQL 中,查询全局状态变量的方式一般有两种:SHOW GLOBAL STATUSperformance_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 少。

状态变量是在哪里定义的?

状态变量的来源主要有三个:

  1. Server 层面的状态变量:这些状态变量主要在status_vars(mysqld.cc)中定义。在 MySQL 8.4 中,共有 321 个状态变量。其中包括了 com_status_vars 中定义的 167 个 Com 相关的变量。
  2. 插件中的状态变量:
    • 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 个。
  3. Component 中的状态变量:例如,在密码认证插件 validate_password 中定义的状态变量。

这些变量会通过add_status_vars函数添加到一个数组中 all_status_vars。注意这个数组名,后面讲解原理时会用到。

```
// sql/mysqld.ccint init_common_variables() {  ...  if (add_status_vars(status_vars)) return 1;   ...}// sql/sql_plugin.ccstatic 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.ccDEFINE_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.ccbool 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,每个元素代表一个状态变量。每个元素包含四个字段,依次是:变量名、变量值、变量类型和变量作用范围。所以,通过元素的第二个字段,就可以确定该状态变量值的来源。

上面列举了四个有代表性的状态变量:

  1. Aborted_clients:变量值来源于全局变量 aborted_threads(extern ulong aborted_threads)。

  2. 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 时使用。
  1. Com:变量值来自于com_status_vars数组(怎么知道它是一个数组呢?实际上看的是第三个字段,SHOW_ARRAY 代表它是一个数组),该数组定义了 Com 相关的状态变量。

  2. 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.ccQuery_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::MaterializeOperand函数实现数据的物化(即构造查询结果),除此之外,这个函数还会逐行读取数据并将其写入目标表。

下面是该函数简化后的代码。

```
bool MaterializeIterator::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::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;}
```

该函数的处理流程如下:

  1. 基于 all_status_vars 构建一个满足条件的状态变量数组 m_show_var_array。至于需要满足什么条件,后面会详细说明。
  2. 将全局的状态变量(global_status_var)和当前所有线程的状态变量(status_var)累加汇总到 status_totals 中。
  3. 遍历 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::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::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 之类的聚合表,还会剔除无法聚合的状态变量。

总结

  1. 状态变量的来源主要有三个:Server、插件和 Component。
  2. 如果想查看某个状态变量值的来源,直接查看定义部分对应元素的第二个字段即可。
  3. 当我们执行 SHOW GLOBAL STATUS 时,实际上查询的是 performance_schema.global_status。
  4. performance_schema.global_status 在实现上主要分为两步:1. 构造查询结果,将所有变量的值存储到一个缓存(m_cache)中;2. 数据读取,直接从缓存中读取变量值。
  5. 之所以 performance_schema.global_status 返回的变量数比 SHOW GLOBAL STATUS 中少,主要是PFS_status_variable_cache::filter_by_name中的限制。
  6. 需要注意的是,如果查询中指定了过滤条件,过滤操作会发生在数据读取阶段,而不是查询结果构造阶段。

文章整理自互联网,只做测试使用。发布者:Lomu,转转请注明出处:https://www.it1024doc.com/5947.html

(0)
LomuLomu
上一篇 2025 年 1 月 12 日 上午4:45
下一篇 2025 年 1 月 12 日 上午5:46

相关推荐

  • Java:IO流详解

    文章目录 基础流 1、IO概述 1.1 什么是IO 1.2 IO的分类 1.3 顶级父类们 2、字节流 2.1 一切皆为字节 2.2 字节输出流 OutputStream 2.3 FileOutputStream类 2.3.1 构造方法 2.3.2 写出字节数据 2.3.3 数据追加续写 2.3.4 写出换行 2.4 字节输入流 InputStream 2.…

    未分类 2025 年 5 月 13 日
    20200
  • manim边学边做–改变动画速度

    ChangeSpeed类是Manim库中用于修改动画速度的类。 它提供了一种灵活的方式来控制动画的播放速度,使动画在不同时间段内以不同的速度播放,从而创造出更加丰富多样的动画效果。 比如,在创建包含多个元素动画的场景中,通过ChangeSpeed可以精确控制不同元素在不同时间点的移动速度,实现复杂的动画节奏编排。 1. 动画概述 与之前介绍的那些动画类不同,…

    2024 年 12 月 31 日
    42300
  • Java 技术新纪元 —— 基于 Java 的联邦学习技术推动跨行业数据协同创新(238)

    🌟亲爱的技术爱好者们,诚挚欢迎访问【青云交的技术天地】!在这个数字化浪潮席卷全球的时代,我们相聚于此共同探索前沿科技。这里不仅是知识分享的平台,更是思想碰撞的舞台,期待与您携手共创技术新篇章!🌟全网平台(微信公众号/CSDN/抖音/华为/支付宝/微博):青云交一、加入【技术精英社群】快速加入通道1:【青云交技术精英圈】快速加入通道2:【CSDN 技术创作交流…

    2025 年 5 月 12 日
    18600
  • 华为OD机试E卷 –数大雁–24年OD统一考试(Java & JS & Python & C & C++)

    文章目录 题目描述 输入描述 输出描述 用例 题目解析 JS算法源码 Java算法源码 python算法源码 c算法源码 题目描述 一群大雁往南飞,给定一个字符串记录地面上的游客听到的大雁叫声,请给出叫声最少由几只大雁发出。具体:1.大雁发出的完整叫声为”quack“,因为有多只大雁同一时间嘎嘎作响,所以字符串中可能会混合多个”quack”2.大雁会依次完整…

    未分类 2025 年 1 月 14 日
    73900
  • Markdown学习

    Markdown学习 (使用软件Typora) 标题 “#”个数加空格,最多支持到六级标题,其中一级标题是最大的 字体 粗体,两边都加**,然后空格 例如粗体 斜体,两边都加*,然后空格 例如 斜体 (思考?斜体加粗怎么实现呢?——三个星号然后空格就行,例如 斜体加粗 ) 删除线,两边都加~~,然后空格 例如~~删除线~~ 引用 一个>加上一个空格,效果如下…

    2025 年 1 月 11 日
    52400

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信