Análisis de fallas | Cómo se perdió un SQL que debería haberse registrado en el registro lento

Autor: Wu Siliang

Un DBA de la industria financiera, entusiastas de la tecnología de bases de datos.

Fuente de este artículo: contribución original

* Producido por la comunidad de código abierto de Aikesheng, no se permite usar el contenido original sin autorización, comuníquese con el editor e indique la fuente para la reimpresión.



fondo

En el entorno de producción, select count(*) from tablela ejecución de la declaración es muy lenta, lo que ha superado con creces long_query_timeel valor de tiempo de consulta lento definido por el parámetro, pero no se registra en el registro lento. También es fácil reproducir este problema en el entorno de prueba y el registro de consultas lentas no registra select count(*)la declaración .

Los parámetros relacionados con la consulta lenta se establecen de la siguiente manera:

slow_query_log = 1                                            #开启慢查询日志
slow_query_log_file = /mydata/3306/log/mysql.slow.log     #慢查询日志文件目录
log_queries_not_using_indexes = 1                           #开启记录未使用索引的SQL
log_slow_admin_statements = 1                               #开启记录管理语句
log_slow_slave_statements = 1                               #开启主从复制中从库的慢查询
log_throttle_queries_not_using_indexes = 10  #限制每分钟写入慢日志的未用索引的SQL的数量
long_query_time = 2                                         #定义慢查询的SQL执行时长
min_examined_row_limit = 100                  #该SQL检索的行数小于100则不会记录到慢日志

select count(*)El principio de ejecución se puede resumir de la siguiente manera: select count(*)cuando , la capa del servidor atraviesa y lee el índice secundario o la clave principal de la capa de InnoDB, y luego cuenta por fila.

Por lo tanto, el registro de consultas lentas no debe registrar declaracioneslong_query_time cuyo tiempo de ejecución exceda .select count(*)

Análisis de código fuente de registro de consulta lento

Para averiguarlo, encontré las siguientes funciones relacionadas para registrar registros de consultas lentas en el código fuente de MySQL.La versión de la base de datos MySQL involucrada en este artículo es 8.0.32.

sql_class.ccupdate_slow_query_statusfunción en el archivo :

void THD::update_slow_query_status() {
  if (my_micro_time() > start_utime + variables.long_query_time)
    server_status |= SERVER_QUERY_WAS_SLOW;
}

my_micro_timeLa función devuelve la hora actual.Si la hora actual es mayor que la hora de inicio de la ejecución de este SQL más la duración definida por long_query_timeel parámetro , server_statusactualice este SQL SERVER_QUERY_WAS_SLOW.

log.ccy funciones log_slow_applicableen el archivo :log_slow_statement

bool log_slow_applicable(THD *thd) {
......

  bool warn_no_index =
      ((thd->server_status &
        (SERVER_QUERY_NO_INDEX_USED | SERVER_QUERY_NO_GOOD_INDEX_USED)) &&
       opt_log_queries_not_using_indexes &&
       !(sql_command_flags[thd->lex->sql_command] & CF_STATUS_COMMAND));
  bool log_this_query =
      ((thd->server_status & SERVER_QUERY_WAS_SLOW) || warn_no_index) &&
      (thd->get_examined_row_count() >= thd->variables.min_examined_row_limit);

  // The docs say slow queries must be counted even when the log is off.
  if (log_this_query) thd->status_var.long_query_count++;

  /*
    Do not log administrative statements unless the appropriate option is
    set.
  */

  if (thd->enable_slow_log && opt_slow_log) {
    bool suppress_logging = log_throttle_qni.log(thd, warn_no_index);

    if (!suppress_logging && log_this_query) return true;
  }
  return false;
}

Determine si SQL cumple las condiciones para registrar registros de consultas lentas:

  1. server_status marcado como SERVER_QUERY_WAS_SLOW warn_no_index  (sin índice utilizado);
  2. El número de filas recuperadas por este SQL >= min_examined_row_limit el número de filas definido por el parámetro.

如果该 SQL 同时满足以上记录慢查询日志的条件,那么则调用 log_slow_do 函数写慢查询日志。

void log_slow_statement(THD *thd) {
  if (log_slow_applicable(thd)) log_slow_do(thd);
}

MySQL 源码调试

在 MySQL 源码的 debug 环境中,开启 gdb 调试,对相关函数打下断点,这样便可以通过跟踪源码弄清楚一条 SQL 记录慢查询日志过程中函数和变量的情况。

(gdb) b THD::update_slow_query_status
(gdb) b log_slow_applicable
// 在客户端执行一条 SQL:select count(*) from user_test,跟踪源码执行到 update_slow_query_status函数时,可以发现这时候这条SQL的执行时长已经超过了long_query_time参数值,并且把这条SQL的server_status更新为SERVER_QUERY_WAS_SLOW。
查看堆栈信息如下:
(gdb) bt
#0  THD::update_slow_query_status (this=0x7f7d6000dcb0) at /root/gdb_mysql/mysql-8.0.32/sql/sql_class.cc:3217
#1  0x000000000329ddaa in dispatch_command (thd=0x7f7d6000dcb0, com_data=0x7f7dc43f1a00, command=COM_QUERY) at /root/gdb_mysql/mysql-8.0.32/sql/sql_parse.cc:2422
#2  0x000000000329a7d3 in do_command (thd=0x7f7d6000dcb0) at /root/gdb_mysql/mysql-8.0.32/sql/sql_parse.cc:1439
#3  0x00000000034b925f in handle_connection (arg=0xc966100) at /root/gdb_mysql/mysql-8.0.32/sql/conn_handler/connection_handler_per_thread.cc:302
#4  0x00000000051e835c in pfs_spawn_thread (arg=0xc9c0940) at /root/gdb_mysql/mysql-8.0.32/storage/perfschema/pfs.cc:2986
#5  0x00007f7ddff35ea5 in start_thread () from /lib64/libpthread.so.0
#6  0x00007f7dde95db0d in clone () from /lib64/libc.so.6
(gdb) n
3218        server_status |= SERVER_QUERY_WAS_SLOW;
(gdb) n
3219    }

跟踪源码执行到 log_slow_applicable 函数时,可以发现函数 thd->get_examined_row_count() 的返回值为 0。也就是说这条 SQL 检索的行数为 0 行,小于当前设置的 min_examined_row_limit 参数值 100,所以这条 SQL 没有记录到慢查询日志中。堆栈信息及打印变量输出如下:

(gdb) bt
#0  log_slow_applicable (thd=0x7f7d6000dcb0) at /root/gdb_mysql/mysql-8.0.32/sql/log.cc:1592
#1  0x00000000038ce8c5 in log_slow_statement (thd=0x7f7d6000dcb0) at /root/gdb_mysql/mysql-8.0.32/sql/log.cc:1661
#2  0x000000000329dff7 in dispatch_command (thd=0x7f7d6000dcb0, com_data=0x7f7dc43f1a00, command=COM_QUERY) at /root/gdb_mysql/mysql-8.0.32/sql/sql_parse.cc:2456
#3  0x000000000329a7d3 in do_command (thd=0x7f7d6000dcb0) at /root/gdb_mysql/mysql-8.0.32/sql/sql_parse.cc:1439
#4  0x00000000034b925f in handle_connection (arg=0xc966100) at /root/gdb_mysql/mysql-8.0.32/sql/conn_handler/connection_handler_per_thread.cc:302
#5  0x00000000051e835c in pfs_spawn_thread (arg=0xc9c0940) at /root/gdb_mysql/mysql-8.0.32/storage/perfschema/pfs.cc:2986
#6  0x00007f7ddff35ea5 in start_thread () from /lib64/libpthread.so.0
#7  0x00007f7dde95db0d in clone () from /lib64/libc.so.6

(gdb) p thd->get_examined_row_count()   //打印 thd->get_examined_row_count() 当前返回值
$4 = 0
(gdb) p thd->variables.min_examined_row_limit //打印 min_examined_row_limit 变量值
$5 = 100

原因

通过跟踪源码,可以查明 select count(*) from table 语句没有写入到慢日志中是因为 MySQL 把此类 SQL 的检索行数计算为 0 行,小于 min_examined_row_limit 参数值。因此,把 min_examined_row_limit 参数设置为 0 后,再次执行 select count(*),可以看到在慢查询日志中,这条 SQL 执行完成后就被记录了。且慢查询日志中的信息显示这条 SQL 检索的行数为 0 行,返回的行数为 1 行。

所以要想把慢的 select count(*) 记录到慢查询日志中,min_examined_row_limit 这个参数必须保持为默认值 0。但是生产环境中一般会开启 log_queries_not_using_indexes 参数,为了避免慢查询日志记录检索行数较少的全表扫描的 SQL,需要设置 min_examined_row_limit 为某个大于 0 的值。

# User@Host: root[root] @ localhost []  Id:     8
# Query_time: 2.833550  Lock_time: 0.000013 Rows_sent: 1  Rows_examined: 0
use testdb;
SET timestamp=1681844004;
select count(*) from user_test;

提交 BUG

在 InnoDB 存储引擎中,每次执行 select count(*) from table 都会遍历全表或二级索引然后统计行数,不应该把 Rows_examined 计算成 0。因此我在官网上提交了此 bug,官方也证实了这个 bug:https://bugs.mysql.com/bug.php?id=110804

MySQL 官方确认 #110804

结语

虽然现在的 MySQL 数据库大多数部署在云上或者使用了数据库管理平台收集慢查询,慢查询日志可能不是首选的排查问题 SQL 的方法。但是对于没有额外配置慢查询监控的 MySQL,慢查询日志仍然是一个非常好的定位慢 SQL 的方法,配合 pt-query-digest 工具使用分析某段时间的 TOP SQL 也十分方便。并且数据库管理平台收集的慢查询数据需要额外的数据库存放,一般都会设置保留一段时间,如果要回溯更早的慢 SQL 就只能通过慢查询日志了。

本文关键字 :#MySQL# #慢查询日志# #源码#

文章推荐:
故障分析 | MySQL 升级到 8.0 变慢问题分析

带你读 MySQL 源码:where 条件怎么过滤记录?

带你读 MySQL 源码:select *

技术分享 | OceanBase 写入限速源码解读

技术分享 | OceanBase 手滑误删了数据文件怎么办



关于 SQLE

爱可生开源社区的 SQLE 是一款面向数据库使用者和管理者,支持多场景审核,支持标准化上线流程,原生支持 MySQL 审核且数据库类型可扩展的 SQL 审核工具。

SQLE 获取

类型 地址
版本库 https://github.com/actiontech/sqle
文档 https://actiontech.github.io/sqle-docs-cn/
发布信息 https://github.com/actiontech/sqle/releases
数据审核插件开发文档 https://actiontech.github.io/sqle-docs-cn/3.modules/3.7_auditplugin/auditplugin_development.html
提交有效 pr,高质量 issue,将获赠面值 200-500 元(具体面额依据质量而定)京东卡以及爱可生开源社区精美周边!
更多关于 SQLE 的信息和交流,请加入官方QQ交流群:637150065


本文分享自微信公众号 - 爱可生开源社区(ActiontechOSS)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

{{o.name}}
{{m.name}}

Supongo que te gusta

Origin my.oschina.net/actiontechoss/blog/8796736
Recomendado
Clasificación