0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

一條本該記錄到慢日志的SQL是如何被漏掉的?

OSC開源社區(qū) ? 來源:愛可生開源社區(qū) ? 2023-06-05 10:40 ? 次閱讀

背景

生產環(huán)境中 select count(*) from table 語句執(zhí)行很慢,已經遠超 long_query_time 參數定義的慢查詢時間值,但是卻沒有記錄到慢日志中。

在測試環(huán)境也很容易復現(xiàn)出該問題,慢查詢日志確實沒有記錄 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執(zhí)行時長
min_examined_row_limit=100#該SQL檢索的行數小于100則不會記錄到慢日志

select count(*) 執(zhí)行原理可以總結如下:InnoDB 存儲引擎在執(zhí)行 select count(*) 時,Server 層遍歷讀取 InnoDB 層的二級索引或主鍵,然后按行計數。

因此,慢查詢日志不應該沒有記錄到執(zhí)行時間超過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 執(zhí)行的開始時間加 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 環(huán)境中,開啟 gdb 調試,對相關函數打下斷點,這樣便可以通過跟蹤源碼弄清楚一條 SQL 記錄慢查詢日志過程中函數和變量的情況。

(gdb)bTHD::update_slow_query_status
(gdb)blog_slow_applicable
// 在客戶端執(zhí)行一條 SQL:select count(*) from user_test,跟蹤源碼執(zhí)行到 update_slow_query_status函數時,可以發(fā)現(xiàn)這時候這條SQL的執(zhí)行時長已經超過了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}

跟蹤源碼執(zhí)行到 log_slow_applicable 函數時,可以發(fā)現(xiàn)函數 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 后,再次執(zhí)行 select count(*),可以看到在慢查詢日志中,這條 SQL 執(zhí)行完成后就被記錄了。且慢查詢日志中的信息顯示這條 SQL 檢索的行數為 0 行,返回的行數為 1 行。

所以要想把慢的 select count(*) 記錄到慢查詢日志中,min_examined_row_limit 這個參數必須保持為默認值 0。

但是生產環(huán)境中一般會開啟 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 存儲引擎中,每次執(zhí)行 select count(*) from table 都會遍歷全表或二級索引然后統(tǒng)計行數,不應該把 Rows_examined 計算成 0。

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

MySQL 官方確認 #110804

結語

雖然現(xiàn)在的 MySQL 數據庫大多數部署在云上或者使用了數據庫管理平臺收集慢查詢,慢查詢日志可能不是首選的排查問題 SQL 的方法。

但是對于沒有額外配置慢查詢監(jiān)控的 MySQL,慢查詢日志仍然是一個非常好的定位慢 SQL 的方法,配合 pt-query-digest 工具使用分析某段時間的 TOP SQL 也十分方便。

并且數據庫管理平臺收集的慢查詢數據需要額外的數據庫存放,一般都會設置保留一段時間,如果要回溯更早的慢 SQL 就只能通過慢查詢日志了。





審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • SQL
    SQL
    +關注

    關注

    1

    文章

    751

    瀏覽量

    43985
  • MYSQL數據庫
    +關注

    關注

    0

    文章

    95

    瀏覽量

    9372
  • GDB調試
    +關注

    關注

    0

    文章

    24

    瀏覽量

    1427

原文標題:一條本該記錄到慢日志的SQL是如何被漏掉的

文章出處:【微信號:OSC開源社區(qū),微信公眾號:OSC開源社區(qū)】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    Database數據庫SQL語句

    如何用一條SQL語句清空數據庫中多張表中的記錄?請大神幫忙,謝謝
    發(fā)表于 03-01 00:57

    Labview 刪除Access數據庫一條記錄,已知刪除記錄的Record index

    Labview 刪除Access數據庫一條記錄,已知刪除記錄的Record index,求解答;最好有實際案例。未安裝Labview SQL工具包
    發(fā)表于 10-19 09:31

    labview如何更新一條記錄呢?、求大神幫助

    labview如何利用自帶數據庫工具包更新一條記錄額,,求大神幫助啊,小弟很急很急,搞了好久了,最好是附帶個例程
    發(fā)表于 11-29 15:39

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

    (Acccess Log)是由應用服務生成的日志,每次API請求都對應一條訪問記錄,內容包括調用者IP、請求的URL、響應延遲、返回狀態(tài)碼、請求和響應字節(jié)數等重要信息。點此查看原文:
    發(fā)表于 02-06 15:24

    Oracle刪除重復記錄只保留一條數據解決步驟

    Oracle刪除重復記錄只保留一條數據的幾種方法
    發(fā)表于 07-05 13:27

    NAS網絡存儲中如何查看日志記錄?

    ;gt; 系統(tǒng)狀態(tài) > 系統(tǒng)記錄; 3.打開系統(tǒng)記錄,可查看所有的日志記錄; 4.選擇查看哪類別
    發(fā)表于 11-06 16:58

    如何打開和關閉日志記錄語句編譯

    我有日志記錄語句,這些語句記錄到串行端口,但我想能夠在全局、給定日志級別和給定宏定義的每個文件的基礎上關閉它們,(DEBUG_FILE)C99允許您使用如下結構:XC16不喜歡這種結構
    發(fā)表于 04-22 11:21

    記錄sql也很慢的原因

    一條記錄sql也很慢的原因
    發(fā)表于 06-15 14:31

    binlog有什么意義/工作模式/優(yōu)缺點

    行數據修改的形式,然后在從端對相同的數據進行修改  優(yōu)點:可以不記錄執(zhí)行SQL語句上下文相關的信息,只記錄一條數據
    發(fā)表于 01-29 17:24

    使用InTouch的SQL Access把數據記錄到Micr

    摘要:本文詳細的描述了如何配置Micsoft Access 2000 ODBC數據源名稱(DSN)及如何使用InTouch當中的SQL Access把數據記錄到Microsoft(MS)Access 數據庫當中。本文包含的圖片是從Window200 Server
    發(fā)表于 07-08 12:39 ?45次下載

    一條SQL語句是怎么被執(zhí)行的

    直是想知道一條SQL語句是怎么被執(zhí)行的,它執(zhí)行的順序是怎樣的,然后查看總結各方資料,就有了下面這篇博文了。 本文將從MySQL總體架構---》查詢執(zhí)行流程---》語句執(zhí)行順序來探討
    的頭像 發(fā)表于 09-12 09:44 ?1472次閱讀
    <b class='flag-5'>一條</b><b class='flag-5'>SQL</b>語句是怎么被執(zhí)行的

    如何將HTTP里面的Header信息記錄到訪問日志

    不得不承認Nginx用的非常廣泛,然而Nginx的訪問日志在分析問題時也有很大用途,那么Nginx日志要想增加些自定義信息就尤為重要了。比如本篇文章就教你如何將HTTP里面的Header信息
    的頭像 發(fā)表于 07-01 11:29 ?2820次閱讀

    SQL優(yōu)化思路與經典案例分析

    如何定位SQL呢、我們可以通過慢查詢日志來查看SQL。默認的情況下呢,MySQL數據庫是不開啟
    的頭像 發(fā)表于 10-27 13:16 ?863次閱讀

    sql優(yōu)化常用的幾種方法

    前言 1.SQL優(yōu)化思路。 1.1 查詢日志記錄SQL
    的頭像 發(fā)表于 11-14 15:04 ?3393次閱讀

    數據庫編程大賽:一條SQL計算撲克牌24點

    參加NineData舉辦的數據庫編程大賽,展示你的技術才華!用一條SQL語句解決撲克牌24點的計算表達式。這場比賽面向數據庫愛好者、開發(fā)者、程序員、DBA等,展示你的編程技能,并有機會贏得豐厚獎勵。歡迎大家來挑戰(zhàn)!
    的頭像 發(fā)表于 12-21 15:24 ?665次閱讀
    數據庫編程大賽:<b class='flag-5'>一條</b><b class='flag-5'>SQL</b>計算撲克牌24點