天天看點

MySQL慢日志slow_log為何會出現Prepare與Binlog DumpMySQL慢日志為何會出現Prepare與Binlog Dump

MySQL慢日志為何會出現Prepare與Binlog Dump

問題現象

某客戶的RDS資料庫的某個時間段(時間點),慢日志記錄出現大量的Prepare操作以及零星的Binlog Dump操作,如下圖:

Prepare:

MySQL慢日志slow_log為何會出現Prepare與Binlog DumpMySQL慢日志為何會出現Prepare與Binlog Dump

Binlog dump:

MySQL慢日志slow_log為何會出現Prepare與Binlog DumpMySQL慢日志為何會出現Prepare與Binlog Dump

問題分析

慢日志中出現Prepare分析

MySQL預處理語句

Prepare是MySQL的預處理指令,其在MySQL server實作了請求的預處理,當我們使用MySQL shell client的時候,可以通過如下方式進行請求:

mysql> PREPARE stmt1 FROM 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse';
mysql> SET @a = 3;
mysql> SET @b = 4;
mysql> EXECUTE stmt1 USING @a, @b;
+------------+
| hypotenuse |
+------------+
|          5 |
+------------+           

當我們在開發應用程式時,在代碼中使用時,可以通過如下方式請求(下圖為JAVA代碼):

String selectSql = "select * from alitest where a = ? or b = ?";
PreparedStatement pretest =  conn.prepareStatement(selectSql);
pretest.setInt(1, 1);
pretest.setInt(2, 2);
ResultSet res = pretest.executeQuery();           
注意:在使用JAVA時,由于預處理既可以在用戶端實作也可以在SERVER側實作,要使程式使用SERVER側的預處理,需要在建連時打開:useServerPrepStmts=true,即:jdbc:mysql://XXX.XXX.XXX.XXX:3306/alitest?useServerPrepStmts=true

MySQL對預處理請求進行Prepare的函數

從/mysql-5.6.24/sql/sql_prepare.cc源碼檔案中,可以找到進行Prepare的函數:Prepared_statement::prepare()。它的主要作用是 Parse statement text, validate the statement, and prepare it for execution。

MySQL記錄慢日志的函數

從/mysql-5.6.24sql/sql_parse.cc源碼檔案中,可以找到進行慢日志記錄的函數:log_slow_statement(THD *thd),它的主要作用是Check whether we need to write the current statement to the slow query log。

複現問題

慢日志中出現Prepare,是否是因為Prepared_statement::prepare()函數處理請求過慢超過long_query_time導緻?為了驗證這個猜測,模拟Prepared_statement::prepare()函數處理慢的情況,人工的通過GDB對Prepared_statement::prepare()設定斷點,增加其執行時間,然後通過MySQL shell client 與JAVA代碼分别請求,檢視慢日志的記錄情況。

設定斷點

(gdb) b Prepared_statement::prepare

Breakpoint 1 at 0x7d821e: file /opt/mysql-5.6.24/sql/sql_prepare.cc, line 3358.

MySQL shell client 執行如下指令

mysql> PREPARE stmt1 FROM 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse';

(指令阻塞......)

指令阻塞後,等待超過long_query_time的時間,然後在GDB中執行continue繼續運作指令,PREPARE指令執行完成後,檢視慢日志如下:

*************************** 15. row ***************************
    start_time: 2020-08-22 18:34:45
     user_host: root[root] @ localhost []
    query_time: 00:04:56
     lock_time: 00:00:00
     rows_sent: 0
 rows_examined: 0
            db:
last_insert_id: 0
     insert_id: 0
     server_id: 2003306
      sql_text: PREPARE stmt1 FROM 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse'
     thread_id: 171           

從慢日志的記錄來看,這不是我們想要的結果。

JAVA執行Prepare請求:

調用JAVA代碼(請記住連接配接時啟用useServerPrepStmts),執行Prepare操作,請求阻塞後,等待超過long_query_time的時間,然後在GDB中執行continue繼續運作指令,JAVA代碼執行完成後,檢視慢日志如下:

************************* 7. row ***************************
    start_time: 2020-08-22 18:57:45
     user_host: dtstest[dtstest] @  [115.216.4.201]
    query_time: 00:00:09
     lock_time: 00:00:00
     rows_sent: 0
 rows_examined: 0
            db: alitest
last_insert_id: 0
     insert_id: 0
     server_id: 2003306
      sql_text: Prepare
     thread_id: 163           

可以看到,Prepare指令被記錄到了慢日志中。

慢日志中出現Binlog dump分析

MySQL的Binlog dump指令

當我們搭建MySQL的從庫時,需要從主庫擷取binlog檔案,而Binlog dump(或者Binlog dump gtid)指令就是用來該功能的。MySQL中有2個關于Binlog dump的函數,分别是com_binlog_dump()與com_binlog_dump_gtid(),後者主要應用于開啟了GTID的複制。可以在/mysql-5.6.24/sql/rpl_master.cc中找到它們。根據之前的猜測,慢日志中出現Binlog dump(或者Binlog dump GTID)是否是因為對應的函數處理慢導緻?

(gdb) b com_binlog_dump

Breakpoint 3 at 0xa12318: file /opt/mysql-5.6.24/sql/rpl_master.cc, line 739.

啟動binlog dump

為了測試友善,直接使用mysqlbinlog程式遠端擷取binlog:

/bin/mysqlbinlog -hxxx.xxx.xxx.xxx -udtstest -p --read-from-remote-server mysqlbin.000004

指令阻塞後,等待超過long_query_time的時間,然後在GDB中執行continue繼續運作指令,mysqlbinlog指令執行完成後,檢視慢日志如下:

************************* 34. row ***************************
    start_time: 2020-08-22 20:57:38
     user_host: dtstest[dtstest] @  [121.196.185.131]
    query_time: 00:00:00
     lock_time: 00:00:00
     rows_sent: 0
 rows_examined: 0
            db:
last_insert_id: 0
     insert_id: 0
     server_id: 2003306
      sql_text: Binlog Dump
     thread_id: 466           

可以看到,Binlog Dump指令被記錄到了慢日志中。

注意:由于Binlog dump屬于admin statement,複現問題時,需要把log_slow_admin_statements設定為ON。慢日志才會記錄admin statements。

問題結論

慢日志中出現Binlog dump、Binlog dump GTID、Prepare指令,是由于對應的請求執行超過long_query_time導緻。出現這種情況,一般意味着系統負載可能較高,尤其是Prepare等指令的出現,需要引起關注。

後記

如果想要更加友善的複現該問題,可以把long_query_time參數設定為0(這樣可以避免通過GDB來手動幹預指令的耗時),然後調用JAVA代碼以及mysqlbinlog程式進行驗證。