目的
為了理解MySQL在執行大SQL時,對執行CTRL+C產生的疑惑,本文通過實驗測試和源碼分析兩個方面,對MySQL處理CTRL+C的詳細過程進行分析和講解,從而解除DBA及開發人員對CTRL+C的誤解。
測試
首先,基於線上數據庫版本,分別使用MySQL客戶端版本5.5.20和5.0.77進行實驗測試,一方面排除不同數據庫客戶端版本造成的差異,另一方面,深入了解不同版本執行CTRL+C產生的差異。
MySQL客戶端5.5.20
使用MySQL客戶端5.5.20在Session1中執行select sleep(100)語句,在Session2中執行show processlist語句;然后在Session1中執行CTRL+C,在Session中執行show processlist語句,查看當前連接的線程。執行的圖如下所示:
Session1:
mysql> select sleep(100); Ctrl-C -- sending "KILL QUERY 153779" to server ... Ctrl-C -- query aborted. ERROR 2013 (HY000): Lost connection to MySQL server during query |
Session2:
mysql> show processlist; mysql> show processlist; |
從以上結果來看,Session1中執行select操作時,Session2中可以查看該連接正在執行,在Session1中執行CTRL+C時,客戶端向服務器端發送KILL QUERY 命令,並且連接關閉。在Session2中可以看到執行select的連接已經關閉。
MySQL客戶端5.0.77
使用客戶端5.0.77執行同樣的操作,執行CTRL+C后,觀察執行的差異性。具體如下表中所示:
Session1:
mysql> select sleep(100); Query aborted by Ctrl+C +------------+ | sleep(100) | +------------+ | 1 | +------------+ |
Session2:
mysql> show processlist; mysql> show processlist; |
從以上結果可知,Session1上執行select時,Session2中建立查詢連接;在Session1中執行CTRL+C時,顯示Query被終止,並且返回執行的錯誤結果。在Session2中可知,連接的線程仍然存在,但是Query被終止,只是保持連接。
源碼分析
為了更進一步對以上測試進行確認,查看MySQL源碼進行進一步的求證,同樣基於MySQL的5.5.20和5.0.77兩個版本進行驗證。具體如下:
MySQL 5.5.20源碼
MySQL客戶端主函數main中,信號函數在源碼文件client/mysql.cc:1163,源碼如下所示:
signal(SIGINT, handle_sigint); // Catch SIGINT to clean up |
信號處理函數handle_sigint在client/mysql.cc:1291,源碼如下所示:
sig_handler handle_sigint(int sig) { char kill_buffer[40]; MYSQL *kill_mysql= NULL; /* terminate if no query being executed, or we already tried interrupting */ if (!executing_query || (interrupted_query == 2)) { tee_fprintf(stdout, "Ctrl-C -- exit!\n"); goto err; } kill_mysql= mysql_init(kill_mysql); if (!mysql_real_connect(kill_mysql,current_host, current_user, opt_password, "", opt_mysql_port, opt_mysql_unix_port,0)) { tee_fprintf(stdout, "Ctrl-C -- sorry, cannot connect to server to kill query, giving up ...\n"); goto err; } interrupted_query++; /* mysqld < 5 does not understand KILL QUERY, skip to KILL CONNECTION */ if ((interrupted_query == 1) && (mysql_get_server_version(&mysql) < 50000)) interrupted_query= 2; /* kill_buffer is always big enough because max length of %lu is 15 */ sprintf(kill_buffer, "KILL %s%lu", (interrupted_query == 1) ? "QUERY " : "", mysql_thread_id(&mysql)); tee_fprintf(stdout, "Ctrl-C -- sending \"%s\" to server ...\n", kill_buffer); mysql_real_query(kill_mysql, kill_buffer, (uint) strlen(kill_buffer)); mysql_close(kill_mysql); tee_fprintf(stdout, "Ctrl-C -- query aborted.\n"); return; err: #ifdef _WIN32 mysql_thread_end(); return; #else mysql_end(sig); #endif } |
從以上源碼可知,MySQL客戶端在捕獲信號后,將KILL QUERY 命令發送到服務器端執行,從而將Query處理KILL。
MySQL 5.0.77源碼
MySQL客戶端主函數main中,信號函數在源碼文件client/mysql.cc:1150。源碼如下所示:
signal(SIGINT, mysql_sigint); // Catch SIGINT to clean up |
信號處理函數mysql_sigint在源碼文件client/mysql.cc:1214,源碼如下所示:
sig_handler mysql_sigint(int sig) { char kill_buffer[40]; MYSQL *kill_mysql= NULL; /* terminate if no query being executed, or we already tried interrupting */ if (!executing_query || interrupted_query++) goto err; kill_mysql= mysql_init(kill_mysql); if (!mysql_real_connect(kill_mysql,current_host, current_user, opt_password, "", opt_mysql_port, opt_mysql_unix_port,0)) goto err; /* kill_buffer is always big enough because max length of %lu is 15 */ sprintf(kill_buffer, "KILL /*!50000 QUERY */ %lu", mysql_thread_id(&mysql)); mysql_real_query(kill_mysql, kill_buffer, strlen(kill_buffer)); mysql_close(kill_mysql); tee_fprintf(stdout, "Query aborted by Ctrl+C\n"); return; err: #ifdef _WIN32 mysql_thread_end(); return; #else mysql_end(sig); #endif } |
通過以上源碼可知,MySQL客戶端捕獲信號后,向服務器端發送KILL /*!50000 QUERY */ 命令並執行,從而將Query處理kill。
基於以上兩個版本處理的源碼可知,MySQL客戶端一定會捕獲CTRL+C信號,並對該信號進行處理。而對於不同版本的客戶端,由於發送命令的不同,導致MySQL服務器端執行結果有所不同。
結論
通過以上測試和源碼分析可知,MySQL客戶端肯定會捕獲CTRL+C信號,並對信號進行處理。不同的是,在MySQL 5.5.20版本的客戶端中執行時,服務器端執行KILL QUERY 命令,將QUERY KILL掉,並將連接關閉。而對MySQL 5.0.77客戶端中執行時,服務器端執行KILL /*!50000 QUERY */ 命令,KILL掉QUERY,但保持連接。
此外,對於update、delete數據更新操作[1],CTRL+C會將執行的操作標記為KILLED狀態,然后執行回滾操作。因此,不會因為CTRL+C操作,導致數據變臟。