0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

一条本该记录到慢日志的SQL是如何被漏掉的?

OSC开源社区 来源:爱可生开源社区 2023-06-05 10:40 次阅读

背景

生产环境中 select count(*) from table 语句执行很慢,已经远超 long_query_time 参数定义的慢查询时间值,但是却没有记录到慢日志中。

在测试环境也很容易复现出该问题,慢查询日志确实没有记录 select count(*) 语句。

慢查询相关参数设置如下:

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(*) 执行原理可以总结如下:InnoDB 存储引擎在执行 select count(*) 时,Server 层遍历读取 InnoDB 层的二级索引或主键,然后按行计数。

因此,慢查询日志不应该没有记录到执行时间超过long_query_time 的 select count(*) 语句。

慢查询日志源码剖析

为了一探到底,在 MySQL 源码中找到了以下记录慢查询日志的相关函数,本文所涉及的 MySQL 数据库版本为 8.0.32。

sql_class.cc 文件中的 update_slow_query_status 函数:

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

my_micro_time 函数返回的是当前时间,如果当前时间大于这条 SQL 执行的开始时间加 long_query_time 参数定义的时长,则更新这条 SQL 的 server_status 为 SERVER_QUERY_WAS_SLOW。

log.cc 文件中的 log_slow_applicable 和 log_slow_statement 函数:

boollog_slow_applicable(THD*thd){
......

boolwarn_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));
boollog_this_query=
((thd->server_status&SERVER_QUERY_WAS_SLOW)||warn_no_index)&&
(thd->get_examined_row_count()>=thd->variables.min_examined_row_limit);

//Thedocssayslowqueriesmustbecountedevenwhenthelogisoff.
if(log_this_query)thd->status_var.long_query_count++;

/*
Donotlogadministrativestatementsunlesstheappropriateoptionis
set.
*/
if(thd->enable_slow_log&&opt_slow_log){
boolsuppress_logging=log_throttle_qni.log(thd,warn_no_index);

if(!suppress_logging&&log_this_query)returntrue;
}
returnfalse;
}

判断该 SQL 是否满足记录慢查询日志的条件:

server_status 标记为 SERVER_QUERY_WAS_SLOW或warn_no_index(没有使用索引);

该 SQL 检索的行数 >= min_examined_row_limit 参数定义的行数。

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

voidlog_slow_statement(THD*thd){
if(log_slow_applicable(thd))log_slow_do(thd);
}

MySQL 源码调试

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

(gdb)bTHD::update_slow_query_status
(gdb)blog_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
#0THD::update_slow_query_status(this=0x7f7d6000dcb0)at/root/gdb_mysql/mysql-8.0.32/sql/sql_class.cc:3217
#10x000000000329ddaaindispatch_command(thd=0x7f7d6000dcb0,com_data=0x7f7dc43f1a00,command=COM_QUERY)at/root/gdb_mysql/mysql-8.0.32/sql/sql_parse.cc:2422
#20x000000000329a7d3indo_command(thd=0x7f7d6000dcb0)at/root/gdb_mysql/mysql-8.0.32/sql/sql_parse.cc:1439
#30x00000000034b925finhandle_connection(arg=0xc966100)at/root/gdb_mysql/mysql-8.0.32/sql/conn_handler/connection_handler_per_thread.cc:302
#40x00000000051e835cinpfs_spawn_thread(arg=0xc9c0940)at/root/gdb_mysql/mysql-8.0.32/storage/perfschema/pfs.cc:2986
#50x00007f7ddff35ea5instart_thread()from/lib64/libpthread.so.0
#60x00007f7dde95db0dinclone()from/lib64/libc.so.6
(gdb)n
3218server_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
#0log_slow_applicable(thd=0x7f7d6000dcb0)at/root/gdb_mysql/mysql-8.0.32/sql/log.cc:1592
#10x00000000038ce8c5inlog_slow_statement(thd=0x7f7d6000dcb0)at/root/gdb_mysql/mysql-8.0.32/sql/log.cc:1661
#20x000000000329dff7indispatch_command(thd=0x7f7d6000dcb0,com_data=0x7f7dc43f1a00,command=COM_QUERY)at/root/gdb_mysql/mysql-8.0.32/sql/sql_parse.cc:2456
#30x000000000329a7d3indo_command(thd=0x7f7d6000dcb0)at/root/gdb_mysql/mysql-8.0.32/sql/sql_parse.cc:1439
#40x00000000034b925finhandle_connection(arg=0xc966100)at/root/gdb_mysql/mysql-8.0.32/sql/conn_handler/connection_handler_per_thread.cc:302
#50x00000000051e835cinpfs_spawn_thread(arg=0xc9c0940)at/root/gdb_mysql/mysql-8.0.32/storage/perfschema/pfs.cc:2986
#60x00007f7ddff35ea5instart_thread()from/lib64/libpthread.so.0
#70x00007f7dde95db0dinclone()from/lib64/libc.so.6

(gdb)pthd->get_examined_row_count()//打印 thd->get_examined_row_count() 当前返回值
$4=0
(gdb)pthd->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.833550Lock_time:0.000013Rows_sent:1Rows_examined:0
usetestdb;
SETtimestamp=1681844004;
selectcount(*)fromuser_test;

提交 BUG

在 InnoDB 存储引擎中,每次执行 select count(*) from table 都会遍历全表或二级索引然后统计行数,不应该把 Rows_examined 计算成 0。

fb6ef690-031b-11ee-90ce-dac502259ad0.png

MySQL 官方确认 #110804

结语

虽然现在的 MySQL 数据库大多数部署在云上或者使用了数据库管理平台收集慢查询,慢查询日志可能不是首选的排查问题 SQL 的方法。

但是对于没有额外配置慢查询监控的 MySQL,慢查询日志仍然是一个非常好的定位慢 SQL 的方法,配合 pt-query-digest 工具使用分析某段时间的 TOP SQL 也十分方便。

并且数据库管理平台收集的慢查询数据需要额外的数据库存放,一般都会设置保留一段时间,如果要回溯更早的慢 SQL 就只能通过慢查询日志了。





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • SQL
    SQL
    +关注

    关注

    1

    文章

    777

    浏览量

    44403
  • MYSQL数据库
    +关注

    关注

    0

    文章

    96

    浏览量

    9532
  • GDB调试
    +关注

    关注

    0

    文章

    24

    浏览量

    1525

原文标题:一条本该记录到慢日志的SQL是如何被漏掉的

文章出处:【微信号:OSC开源社区,微信公众号:OSC开源社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    相关推荐

    Database数据库SQL语句

    如何用一条SQL语句清空数据库中多张表中的记录?请大神帮忙,谢谢
    发表于 03-01 00:57

    Labview 删除Access数据库一条记录,已知删除记录的Record index

    Labview 删除Access数据库一条记录,已知删除记录的Record index,求解答;最好有实际案例。未安装Labview SQL工具包
    发表于 10-19 09:31

    labview如何更新一条记录呢?、求大神帮助

    labview如何利用自带数据库工具包更新一条记录额,,求大神帮助啊,小弟很急很急,搞了好久了,最好是附带个例程
    发表于 11-29 15:39

    API信息全掌控,方便你的日志管理——阿里云推出API网关打通日志服务

    (Acccess Log)是由应用服务生成的日志,每次API请求都对应一条访问记录,内容包括调用者IP、请求的URL、响应延迟、返回状态码、请求和响应字节数等重要信息。点此查看原文:
    发表于 02-06 15:24

    Oracle删除重复记录只保留一条数据解决步骤

    Oracle删除重复记录只保留一条数据的几种方法
    发表于 07-05 13:27

    NAS网络存储中如何查看日志记录

    ;gt; 系统状态 > 系统记录; 3.打开系统记录,可查看所有的日志记录; 4.选择查看哪类别
    发表于 11-06 16:58

    如何打开和关闭日志记录语句编译

    我有日志记录语句,这些语句记录到串行端口,但我想能够在全局、给定日志级别和给定宏定义的每个文件的基础上关闭它们,(DEBUG_FILE)C99允许您使用如下结构:XC16不喜欢这种结构
    发表于 04-22 11:21

    记录sql也很慢的原因

    一条记录sql也很慢的原因
    发表于 06-15 14:31

    binlog有什么意义/工作模式/优缺点

    行数据修改的形式,然后在从端对相同的数据进行修改  优点:可以不记录执行SQL语句上下文相关的信息,只记录一条数据
    发表于 01-29 17:24

    使用InTouch的SQL Access把数据记录到Micr

    摘要:本文详细的描述了如何配置Micsoft Access 2000 ODBC数据源名称(DSN)及如何使用InTouch当中的SQL Access把数据记录到Microsoft(MS)Access 数据库当中。本文包含的图片是从Window200 Server
    发表于 07-08 12:39 45次下载

    一条SQL语句是怎么被执行的

    直是想知道一条SQL语句是怎么被执行的,它执行的顺序是怎样的,然后查看总结各方资料,就有了下面这篇博文了。 本文将从MySQL总体架构---》查询执行流程---》语句执行顺序来探讨
    的头像 发表于 09-12 09:44 1587次阅读
    <b class='flag-5'>一条</b><b class='flag-5'>SQL</b>语句是怎么被执行的

    如何将HTTP里面的Header信息记录到访问日志

    不得不承认Nginx用的非常广泛,然而Nginx的访问日志在分析问题时也有很大用途,那么Nginx日志要想增加些自定义信息就尤为重要了。比如本篇文章就教你如何将HTTP里面的Header信息
    的头像 发表于 07-01 11:29 3044次阅读

    SQL优化思路与经典案例分析

    如何定位SQL呢、我们可以通过慢查询日志来查看SQL。默认的情况下呢,MySQL数据库是不开启
    的头像 发表于 10-27 13:16 1035次阅读

    sql优化常用的几种方法

    前言 1.SQL优化思路。 1.1 查询日志记录SQL
    的头像 发表于 11-14 15:04 5427次阅读

    数据库编程大赛:一条SQL计算扑克牌24点

    参加NineData举办的数据库编程大赛,展示你的技术才华!用一条SQL语句解决扑克牌24点的计算表达式。这场比赛面向数据库爱好者、开发者、程序员、DBA等,展示你的编程技能,并有机会赢得丰厚奖励。欢迎大家来挑战!
    的头像 发表于 12-21 15:24 831次阅读
    数据库编程大赛:<b class='flag-5'>一条</b><b class='flag-5'>SQL</b>计算扑克牌24点