网易首页 > 网易号 > 正文 申请入驻

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

0
分享至

作者:吴斯亮

某金融行业 DBA,数据库技术爱好者。

背景

生产环境中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_timeselect count(*)语句。

慢查询日志源码剖析

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

sql_class.cc文件中的update_slow_query_status函数:

void THD::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_statusSERVER_QUERY_WAS_SLOW

log.cc文件中的log_slow_applicablelog_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;
}

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

  1. server_status标记为SERVER_QUERY_WAS_SLOWwarn_no_index(没有使用索引);

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

如果该 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# #慢查询日志# #源码#

QR 码编解码库、二维码生成/扫描工具

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
“将是场非常不幸的峰会”,外媒担忧:G7几乎全部人人“自身难保”

“将是场非常不幸的峰会”,外媒担忧:G7几乎全部人人“自身难保”

观察者网
2024-06-13 21:56:15
多看看美女有益身心健康

多看看美女有益身心健康

室内设计师阿喇
2024-06-13 15:37:16
最高赔偿超2w/㎡!成都这些人发财了!

最高赔偿超2w/㎡!成都这些人发财了!

楼市灭霸
2024-06-13 09:29:51
你无意中摸过什么不该摸的东西?网友:对视的那一刻,我俩都懵了

你无意中摸过什么不该摸的东西?网友:对视的那一刻,我俩都懵了

文雅笔墨
2024-06-13 20:51:06
17.59万起!深蓝G318上市即爆火:60分钟订单破3000台

17.59万起!深蓝G318上市即爆火:60分钟订单破3000台

快科技
2024-06-13 22:13:25
中国外贸发生大变化!高中低端市场全部失守,依赖我国消费者?

中国外贸发生大变化!高中低端市场全部失守,依赖我国消费者?

科普六点半
2024-06-04 09:27:58
厦门有大动作,台军一把手脸色都变了,放豪言:我要找大陆说清楚

厦门有大动作,台军一把手脸色都变了,放豪言:我要找大陆说清楚

千里持剑
2024-06-12 15:00:30
我突然理解,为啥上海女孩子真不会精致土

我突然理解,为啥上海女孩子真不会精致土

小虎新车推荐员
2024-06-13 17:30:33
基德:东契奇必须能防守 但也要明白我们会在他被攻击时保护他

基德:东契奇必须能防守 但也要明白我们会在他被攻击时保护他

直播吧
2024-06-13 16:33:13
阶级斗争熄灭论实现了,胡汉三回来了

阶级斗争熄灭论实现了,胡汉三回来了

雪中风车
2024-06-10 18:09:17
日本顶级财阀的联姻:丰田家族第五代继承人,迎娶女星“小赫本”

日本顶级财阀的联姻:丰田家族第五代继承人,迎娶女星“小赫本”

回京历史梦
2024-05-28 12:48:59
通报批评的好!吉林银行取2万需派出所同意后续,律师发声:违法

通报批评的好!吉林银行取2万需派出所同意后续,律师发声:违法

户外阿崭
2024-06-13 14:00:08
印度真的有网上说的那么脏乱差嘛?网友评论一针见血,给我笑麻了

印度真的有网上说的那么脏乱差嘛?网友评论一针见血,给我笑麻了

开玩笑的水母
2024-06-11 17:55:57
A股:又有消息来了,A股会继续下跌还是开启大涨?

A股:又有消息来了,A股会继续下跌还是开启大涨?

财经大拿
2024-06-14 03:30:02
庾澄庆这是怎么了?突然一下就老了,而且越来越像于谦

庾澄庆这是怎么了?突然一下就老了,而且越来越像于谦

盛华名阁汇
2024-06-12 14:00:40
IMF最新评估发现:人民币国际化出现停滞

IMF最新评估发现:人民币国际化出现停滞

长平投研
2024-06-13 22:06:40
孙楠冲上热搜!身高178,体重130斤引爆笑,坐韩红对面瘦到认不出

孙楠冲上热搜!身高178,体重130斤引爆笑,坐韩红对面瘦到认不出

山野下
2024-06-12 08:33:54
两性羞羞:添女友这里,她会嗨到不行

两性羞羞:添女友这里,她会嗨到不行

坟头长草
2024-05-30 16:23:58
《玫瑰的故事》直到黄亦玫遇到方协文的床,她都不知庄国栋错在哪

《玫瑰的故事》直到黄亦玫遇到方协文的床,她都不知庄国栋错在哪

小邵说剧
2024-06-13 21:00:42
60岁的夫妻,一周同房几次比较好?60岁绝经大姐一周一次,如何?

60岁的夫妻,一周同房几次比较好?60岁绝经大姐一周一次,如何?

39健康网
2024-05-19 23:20:03
2024-06-14 04:16:49
开源中国
开源中国
每天为开发者推送最新技术资讯
6324文章数 34066关注度
往期回顾 全部

科技要闻

小红书员工仅1/5工龄满2年 32岁就不让进了

头条要闻

上海楼市新政后有业主熬夜卖房:比之前最低价高360万

头条要闻

上海楼市新政后有业主熬夜卖房:比之前最低价高360万

体育要闻

乔丹最想单挑的男人走了

娱乐要闻

森林北报案,称和汪峰的感情遭受压力

财经要闻

私募大佬孙强:中国为什么缺少耐心资本

汽车要闻

升级8155芯片 新款卡罗拉锐放售12.98-18.48万

态度原创

健康
数码
本地
房产
教育

晚餐不吃or吃七分饱,哪种更减肥?

数码要闻

1999元起!飞米MINI 3无人机小米有品开售:4K录制、32分钟续航

本地新闻

粽情一夏|海河龙舟赛,竟然成了外国人的大party!

房产要闻

再度告急!海口连续仨月住宅入市不足千套!竟有楼盘卖爆!

教育要闻

TTS新传论文带读:粉过恋爱兄妹的李龙宇是我的赛博案底…

无障碍浏览 进入关怀版