天天看點

源碼解析MySQL慢日志slow_log記錄相關函數與邏輯 源碼解析MySQL慢日志記錄相關函數與邏輯

源碼解析MySQL慢日志記錄相關函數與邏輯

操作環境

資料庫版本:mysql-5.6.24 source code

作業系統版本:CentOS Linux release 7.6.1810 (Core)

以下主要函數的代碼檔案:

/mysql-5.6.24/sql/sql_class.h

/mysql-5.6.24/sql/sql_parse.cc

慢日志記錄相關函數與邏輯

THD::update_server_status()函數

函數體

void update_server_status()

{

ulonglong end_utime_of_query= current_utime();

if (end_utime_of_query > utime_after_lock + variables.long_query_time)

server_status|= SERVER_QUERY_WAS_SLOW;

}

該函數主要進行慢查詢的判斷以及更新server_status,它判斷慢查詢的條件是:

end_utime_of_query > utime_after_lock + variables.long_query_time

其中:

end_utime_of_query來自于current_utime(),代碼ulonglong end_utime_of_query= current_utime()進行了指派。current_utime()是用來擷取目前時間的,調用的是Linux作業系統(目前是linux系統)gettimeofday()函數。

utime_after_lock:這個值是指該SQL鎖之後的時間,把SQL等待鎖之後的時間作為真正的執行開始時間。假設SQL執行開始時間是1598532179899361(微秒),鎖定時間假設進行了20秒(long_query_time為1秒)。而此處的utime_after_lock的值是1598532179899361+20秒的時間。這樣變相的說明了,慢日志記錄的SQL在進行耗時判斷時,是不記錄鎖的(而MDL鎖的時間是計算在内的,具體原因後面嘗試讨論)。

variables.long_query_time:這是我們設定的long_query_time的時間。

是以這個函數主要是判斷SQL的執行完成的時間加上long_query_time是否超過了目前時間(end_utime_of_query),以此判斷是否是慢SQL,如果超過,則執行 server_status|= SERVER_QUERY_WAS_SLOW進行按位或且指派運算(相當于server_status=server_status|SERVER_QUERY_WAS_SLOW),其中Mysql代碼中定義SERVER_QUERY_WAS_SLOW值為2048,server_status是不确定的,會根據我們的SQL和使用方式進行指派,我們測試時此處是2,則它們進行計算後,server_status值為2050(下面的log_slow_applicable()函數會用到)。

log_slow_statement()函數

void log_slow_statement(THD *thd)

DBUG_ENTER("log_slow_statement");

if (log_slow_applicable(thd))

log_slow_do(thd);

DBUG_VOID_RETURN;

update_server_status()函數判斷完之後,繼續執行,後面會執行log_slow_statement()函數,這個函數主要邏輯是:通過log_slow_applicable()判斷該慢SQL是否可以記錄到慢SQL裡(具體邏輯下面說明),如果log_slow_applicable(thd)是true,則執行 log_slow_do(thd);

log_slow_applicable()函數

bool (THD *thd)

DBUG_ENTER("log_slow_applicable");

if (unlikely(thd->in_sub_stmt))

DBUG_RETURN(false); // Don't set time for sub stmt

if (thd->enable_slow_log)

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);

bool suppress_logging= log_throttle_qni.log(thd, warn_no_index);

if (!suppress_logging && log_this_query)

DBUG_RETURN(true);

DBUG_RETURN(false);

該函數先判斷是否開啟了慢日志記錄(if (thd->enable_slow_log)),如果開啟,則進行如下判斷:

一:判斷warn_no_index是true還是false,主要判斷:

​ 1, 使用thd->server_status和(SERVER_QUERY_NO_INDEX_USED|SERVER_QUERY_NO_GOOD_INDEX_USED)的值進行二進制的"位與&"運算,判斷是否沒有使用索引或者沒有使用GOOD INDEX。其中SERVER_QUERY_NO_GOOD_INDEX_USED 值為16,SERVER_QUERY_NO_INDEX_USED值為32,thd->server_status前面已經計算過是2050。最終的運算式是:2050&(32|16),值為0。

​ 2,opt_log_queries_not_using_indexes 确認MySQL Server是否開啟了log_queries_not_using_indexes參數。此處未開啟,值為0。

​ 3,sql_command_flags[thd->lex->sql_command]和CF_STATUS_COMMAND)進行二進制的"位與&"運算,判斷SQL的指令flag是否是CF_STATUS_COMMAND,然後進行"邏輯非 !"運算,其中sql_command_flags[thd->lex->sql_command]的值是變化的,會根據SQL類型不同而不同,此處由于我們使用的測試指令是show processlist,其值為4,CF_STATUS_COMMAND值為1U << 2(十進制是4)。最終的運算式是:4&4,值為4。進行"邏輯非 !"運算後是false。MySQL用這個條件來過濾admin_statements(slow log的參數log_slow_admin_statements)。隻有當log_slow_admin_statements打開時,admin_statements進行比較時它才會為true。

最終的運算式是:0&&0&&false,值為false,這三個條件是"邏輯與&&"的關系,也就是三個條件必須全部是true,最終的warn_no_index才是true。

二:判斷log_this_query是true還是false,主要判斷:

​ 1,使用(thd->server_status & SERVER_QUERY_WAS_SLOW) 與warn_no_index的值進行"邏輯或||"運算。先對thd->server_status 和 SERVER_QUERY_WAS_SLOW兩個的值進行二進制的"位與&"運算,其中thd->server_status值前面已經計算過為2050,SERVER_QUERY_WAS_SLOW值為2048,最終的運算式是:2050&2048,值為2048。然後再與warn_no_index進行"邏輯或||"運算,由于warn_no_index前面已經得出是false。2048||false的值為true。也就是這個表達式(thd->server_status & SERVER_QUERY_WAS_SLOW) ||warn_no_index)的值為true。

​ 2,使用thd->get_examined_row_count()與thd->variables.min_examined_row_limit比較,确認前者大于等于後者。這個主要是為了判斷目前的慢查詢的檢索行數是否大于MySQL Server設定的變量min_examined_row_limit。由于我們測試時,min_examined_row_limit變量值并未進行設定,是以variables.min_examined_row_limit的值為0,此處表達式的結果為true。

最終的運算式是:true&&true,值為true。

三:判斷是否要對慢日志進行suppress_logging,這個對應的MySQL server的log_throttle_queries_not_using_indexes 參數,由于我們測試時,其值并未設定,是以此處表達式的結果為false。

以上3個條件判斷完成後,進行return的判斷:

由于suppress_logging 值為false,是以!suppress_logging 值為true。log_this_query也是為true。最終條件成立,函數log_slow_applicable()函數傳回true。

log_slow_do()函數

void log_slow_do(THD *thd)

DBUG_ENTER("log_slow_do");

THD_STAGE_INFO(thd, stage_logging_slow_query);

thd->status_var.long_query_count++;

if (thd->rewritten_query.length())

slow_log_print(thd,

​ thd->rewritten_query.c_ptr_safe(),

​ thd->rewritten_query.length());

else

slow_log_print(thd, thd->query(), thd->query_length());

log_slow_applicable(thd)傳回為true,進入log_slow_do()函數。該函數主要進行慢日志的寫入操作。這個函數會調用slow_log_print()函數。

至此,一個SQL的慢日志判斷過程完成。