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

news/2025/1/7 16:02:45/文章来源:https://www.cnblogs.com/ivictor/p/18654297

问题

在 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.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,每个元素代表一个状态变量。每个元素包含四个字段,依次是:变量名、变量值、变量类型和变量作用范围。所以,通过元素的第二个字段,就可以确定该状态变量值的来源。

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

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

  4. 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;
}

该函数的处理流程如下:

  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<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 之类的聚合表,还会剔除无法聚合的状态变量。

总结

  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. 需要注意的是,如果查询中指定了过滤条件,过滤操作会发生在数据读取阶段,而不是查询结果构造阶段。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/864806.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

将 EasySQLite 从 .NET 8 升级到 .NET 9

前言 EasySQLite是一个.NET 8操作SQLite入门到实战的详细教程,主要是对学校班级,学生信息进行管理维护。今天咱们的主要内容是将EasySQLite从.NET 8升级到.NET 9。GitHub开源地址:https://github.com/YSGStudyHards/EasySQLite选型、开发详细教程第一天 SQLite 简介 第二天 …

IDA Pro 9.0 (macOS, Linux, Windows) - 强大的反汇编程序、反编译器和多功能调试器

IDA Pro 9.0 (macOS, Linux, Windows) - 强大的反汇编程序、反编译器和多功能调试器IDA Pro 9.0 (macOS, Linux, Windows) - 强大的反汇编程序、反编译器和多功能调试器 A powerful disassembler, decompiler and a versatile debugger. In one tool. 请访问原文链接:https://…

使用DockerCompose部署服务

由于格式或图片解析问题,为了更好的阅读体验,可前往 阅读原文以前我们总是用命令管理每个容器的启动、停止等等,若有多个容器时可能还存在启动优先级的问题,那就要等到指定的容器启动后再去启动另一个容器,对于整体的应用服务管理极其不方便,简单的docker run命令更适合初…

读数据保护:工作负载的可恢复性27传统的数据保护方案

传统的数据保护方案1. 传统的数据保护方案 1.1. 备份行业有一个比较特殊的地方在于,10年或20年前设计的一些产品至今仍然有许多人在用 1.2. 在20年前,市面上的所有备份方案都是我们现在称之为传统备份产品的那种方案1.2.1. 必须自己动手把上百个UNIX系统里的数据各自备份到单…

DateTimeExtensions:一个轻量C#的开源DateTime扩展方法库

推荐一个专门为System.DateTime编写的扩展方法库。 01 项目简介 该项目主要是为System.DateTime和System.DateTimeOffset的编写的扩展方法,包括自然日期差值的文本表示(精确和人性化四舍五入)、多个时区的节假日和工作日计算。 核心扩展方法有: 1、DateTimeOffset和DateTim…

OpenVX的基本操作与支持树莓派联合开发

OpenVX支持树莓派联合开发 Khronos集团和树莓派共同致力于OpenVX的开源实现™11.3,通过了树莓派的一致性。通过一致性配置文件,开源实现了树莓派上OpenVX 1.3中指定的视觉、增强视觉和神经网络。 当Khronos标准在目标系统上可用时,应用程序开发人员可以始终自由使用这些标准…

推荐4本书《AI芯片开发核心技术详解》、《智能汽车传感器:原理设计应用》、《TVM编译器原理与实践》、《LLVM编译器原理与实践》

4本书推荐《AI芯片开发核心技术详解》、《智能汽车传感器:原理设计应用》、《TVM编译器原理与实践》、《LLVM编译器原理与实践》由清华大学出版社资深编辑赵佳霓老师策划编辑的新书《AI芯片开发核心技术详解》已经出版,京东、淘宝天猫、当当等网上,相应陆陆续续可以购买。该…

技术架构典型技术选型

技术架构由多种技术组成,过程中可能涉及非常多的具体技术【图】技术架构核心技术 下面我们就技术架构中核心的流量调度、服务治理、监控体系、消息列队、微服务技术框架等进一步展开介绍。 一、流量调度 流量调度是技术架构中的核心技术,包括负载均衡、API网关、配置中心,以…

什么是单向认证与双向认证

什么是SSL双向认证,与单向认证证书有什么区别 SSL/TLS 证书是用于用户浏览器和网站服务器之间的数据传输加密,实现互联网传输安全保护,大多数情况下指的是服务器证书。服务器证书是用于向浏览器客户端验证服务器,这种是属于单向认证的SSL证书。但是,如果服务器需要对客户端…

「杂文」日常 11

基于手机相册的 2024 年度总结好像一年的开头是考试周来着非常卓越的年轻就是好啊,骑车跨越半个城区去吃包子当时还是狂热粥批 给春节活动攒了大量抽嫖同学的桌游寒假打了不少生稀盐酸看起来还挺有精神的()被 jbbai 带着入坑铲了 当时那个段位乱 D 凑大羁绊就爽吃了因为看到…

块存储、文件存储、对象存储的比较分析

【摘要】本文从从应用角度比较块存储、文件存储、对象存储,对三者的层次关系进行了清晰的解读,并比较了分布式存储在块存储、文件存储、对象存储的应用成效。 一、块存储、文件存储、对象存储三者的本质差别 1.1 块存储 典型设备:磁盘阵列,硬盘 块存储主要是将裸磁盘空间整…

分析基于ASP.NET Core Kernel的gRPC服务在不同.NET版本的不同部署方式的不同线程池下的性能表现

分析基于ASP.NET Core Kernel的gRPC服务在不同.NET版本的不同部署方式的不同线程池下的性能表现 使用默认的 gRPC 项目模板创建,垃圾回收器类型为 ServerGC(Server garbage collection)。 使用 ghz 工具在不同的请求总数、连接数、并发数的参数下,进行压力测试,接口为 /gree…