在平時使用MySQL的過程中,不知道你是否遇到一個查詢很長時間沒有出結果,這個時候,你可能會:執行kill query 線程id來終止這個查詢語句;或者,kill connectIOn 線程id斷開連接;又或者,Ctrl C結束線程終止查詢。
但是,是不是會發現一個奇怪的現象,可能你在客戶端感覺不到,但是這個時候如果有交易并且涉及剛剛查詢的表,可能就會超時,看binlog日志會發現查詢并沒有結束?這是為什麼呢?
其實大多數情況下,kill query/connection 命令是有效的。比如,執行一個查詢的過程中,發現執行時間太久,要放棄繼續查詢,這時我們就可以用 kill query 命令,終止這條查詢語句。
還有一種情況是,語句處于鎖等待的時候,直接使用 kill 命令也是有效的。如下:
但是,我們思考一下:session B 是直接終止掉線程,什麼都不管就直接退出嗎?顯然,這是不行的。
線程收到kill指令後做什麼呢?了解一定MySQL的人應該都知道,當對一個表做增删改查操作時,會在表上加 MDL 讀鎖。上圖session B 雖然處于 blocked 狀态,但還是拿着一個 MDL 讀鎖的。如果線程被 kill 的時候,就直接終止,那之後這個 MDL 讀鎖就沒機會被釋放了。
由此,我們可以了解到,線程收到kill指令後并不會馬上停止,而是會等執行完當前的操作才會停止。
其實,這跟 Linux 的 kill 命令類似,kill -N pid 并不是讓進程直接停止,而是給進程發一個信号,然後進程處理這個信号,進入終止邏輯。隻是對于 MySQL 的 kill 命令來說,不需要傳信号量參數,就隻有“停止”這個命令。
kill query thread_id過程MySQL線程收到kill query thread_id_B指令後,做了兩件事:
kill不掉的查詢-線程沒有執行到判斷線程狀态
- 把 session B 的運行狀态改成 THD::KILL_QUERY(将變量 killed 賦值為 THD::KILL_QUERY);
- 給 session B 的執行線程發一個信号。發一個信号的目的,就是讓 session B 退出等待,來處理這個 THD::KILL_QUERY 狀态。
- 前提
setglobal InnoDB_thread_concurrency=2,将 InnoDB 的并發線程上限數設置為 2;
可以看到:
- sesssion C 執行的時候被堵住了;
- 但是 session D 執行的 kill query C 命令卻沒什麼效果,
- 直到 session E 執行了 kill connection 命令,才斷開了 session C 的連接,提示“Lost connection to MySQL server during query”
- 但是這時候,如果在 session E 中執行 show processlist,你就能看到下面這個圖。
在這個例子裡,12 号線程的等待邏輯是這樣的:每 10 毫秒判斷一下是否可以進入 InnoDB 執行,如果不行,就調用 nanosleep 函數進入 sleep 狀态。
也就是說,雖然 12 号線程的狀态已經被設置成了 KILL_QUERY,但是在這個等待進入 InnoDB 的循環過程中,并沒有去判斷線程的狀态,因此根本不會進入終止邏輯階段。
那為什麼執行 show processlist 的時候,會看到 Command 列顯示為 killed 呢?其實,這就是因為在執行 show processlist 的時候,有一個特别的邏輯:
如果一個線程的狀态是KILL_CONNECTION,就把Command列顯示成Killed。
這個例子是 kill 無效的第一類情況,即:線程沒有執行到判斷線程狀态的邏輯。跟這種情況相同的,還有由于 IO 壓力過大,讀寫 IO 的函數一直無法返回,導緻不能及時判斷線程的狀态。
kill不掉的查詢-終止邏輯耗時較長這類情況常見的幾種場景:
Ctrl C
- 超大事務執行期間被 kill。這時候,回滾操作需要對事務執行期間生成的所有新數據版本做回收操作,耗時很長。
- 大查詢回滾。如果查詢過程中生成了比較大的臨時文件,加上此時文件系統壓力大,删除臨時文件可能需要等待 IO 資源,導緻耗時較長。
- DDL 命令執行到最後階段,如果被 kill,需要删除中間過程的臨時文件,也可能受 IO 資源影響耗時較久。
相信大家一定在客戶端執行查詢時,使用過Ctrl C終止查詢吧,在客戶端看起來是終止,但是,其實查詢并不一定立馬終止了的。
其實在客戶端的操作隻能操作到客戶端的線程,客戶端和服務端隻能通過網絡交互,是不可能直接操作服務端線程的。
而由于 MySQL 是停等協議,所以這個線程執行的語句還沒有返回的時候,再往這個連接裡面繼續發命令也是沒有用的。
實際上,執行 Ctrl C 的時候,是 MySQL 客戶端另外啟動一個連接,然後發送一個 kill query 命令。所以,Ctrl C最終還是要執行kill query的流程的。
總結今天,給大家介紹了一些kill不掉的情況,這些“kill 不掉”的情況,其實是因為發送 kill 命令的客戶端,并沒有強行停止目标線程的執行,而隻是設置了個狀态,并喚醒對應的線程。而被 kill 的線程,需要執行到判斷狀态的“埋點”,才會開始進入終止邏輯階段。并且,終止邏輯本身也是需要耗費時間的。
所以,如果你發現一個線程處于 Killed 狀态,你可以做的事情就是,通過影響系統環境,讓這個 Killed 狀态盡快結束。
另外,這裡還有一點要跟大家強調下的,也是我在實際工作中的了解:
- 如果我們要查詢一些數據,盡量不要查詢主庫,因為主庫上有很多交易的寫操作,盡量在從庫執行,影響小一些。
- 不要在生産執行複雜的大查詢,即使是從庫也是會影響到正常交易的讀操作的。
- 如果真的需要周期性查詢一些數據,可以選擇從大數據中心的備份數據裡讀取,大數據中心一般都會同步生産系統的相關表和數據的。或者可以通過程序在非交易時間段(或交易量少的時候)進行查詢。
以上,希望有幫助到大家,在客戶端執行查詢一定要先分析sql是否可以在生産執行呢~可以先在準生産環境先執行也是能防患未然的呢。
(歡迎關注留言~一起學習進步呀)
,