kill 指令的執行原理

kill 指令有兩種寫法 " kill query + 線程 id "、" kill connection(可缺省) + 線程 id "。分別表示關閉指定線程正在執行的語句、斷開指定線程鏈接的客戶端(若是有正在執行的操做會先中止執行的操做再關閉鏈接)。但某些狀況下使用 kill query 後使用 show processlist 查看 Command 列爲 killed(表示 正在等待回收線程回收,還未回收),這是爲何呢?mysql

在解答這個問題前,須要知道服務器端處理請求的線程是如何執行的,以及 kill 命令是如何做用的。sql

Kill 指令執行原理

指令執行特色

一、 一個語句執行過程當中有多處 " 埋點 ",在這些 " 埋點 " 的地方判斷線程狀態,若是發現線程狀態是 THD:KILL_QUERY,纔開始進入語句終止邏輯;數據庫

二、若是處於等待狀態,必須是一個能夠被喚醒的等待,不然根本不會執行到「埋點」處;緩存

三、語句從開始進入終止邏輯,到終止邏輯徹底完成,是有一個過程的。服務器

 

kill query 執行原理

kill query 主要進行了兩步操做:網絡

一、把線程的運行狀態改爲 THD::KILL_QUERY(將變量 killed 賦值爲 THD::KILL_QUERY);session

二、給會話的執行線程發一個信號,退出阻塞狀態,處理這個狀態。併發

 

Kill Connection 執行原理

一、把 12 號線程狀態設置爲 KILL_CONNECTION;socket

二、關掉 12 號線程的網絡鏈接。函數

 

是否能夠被中斷判斷

一、通常正常執行的語句在執行 kill query 後都會先將狀態從 killed 改爲 KILL_QUERY,而後執行到 " 埋點 " 處被判斷中斷執行。

二、若是是處於阻塞的語句,那麼須要去查看當前阻塞等待的狀態是否能夠被喚醒,若是能夠被喚醒纔有機會中斷當前語句。

能夠被中斷的場景:正常執行或者處於能夠被喚醒的阻塞等待狀態。

例子:因行鎖阻塞。

由於等行鎖時,使用的是 pthread_cond_timedwait 函數,因此這個等待狀態能夠被喚醒。能夠被 kill query 直接喚醒繼續執行直到 "埋點" 判斷。

 

不能夠被中斷的場景:被阻塞且不能被喚醒。

例子:因併發線程被使用完而形成的阻塞。

將參數 innodb_thread_concurrency(MySQL 的併發線程數)設爲 2。而後執行下面的操做:

在 sessionD 執行 kill query C 後 sessionC 並無退出阻塞。

問題1:爲何使用 kill query 沒有中斷阻塞?

答:由於這種阻塞從微觀上來看並非阻塞,而是一種循環判斷。每隔 10 毫秒判斷一下是否能夠進入 Innodb 執行,若是不行,就調用 nanosleep 函數進入 sleep 狀態。也就是說,雖然線程的狀態已經被設置成了 KILL_QUERY(THD::KILL_QUERY),可是在這個等待進入 InnoDB 的循環過程當中,並無執行到 "埋點",也就沒有去判斷線程的狀態,所以根本不會進入終止邏輯階段。因此也就不會中斷。

問題2:若是此時使用 show processlist 來查看,會發現 Command 列爲 killed,這是爲何?

答:kill query 語句會將線程狀態設爲 KILL_QUERY ,這時會由於這個狀態而被判斷爲正在執行中斷邏輯,因此 Command 值爲 killed。

問題3:爲何使用 kill connection 能夠中斷阻塞?

答:由於 kill connection 會直接關閉線程的網絡鏈接,強制關閉,因此這時候 session C 收到了斷開鏈接的提示。

問題4:若是隻是使用 kill query 何時才能中斷阻塞?

答:只有等到會話被分配了線程後執行到 「 埋點 」 後判斷而後執行中斷邏輯纔會退出。而被分配線程後並非就必定會中斷,若是在執行到 "埋點" 以前讓出線程,那麼就會再次等待。MySQL 的線程是多路複用的

 

其餘

一、其實除了上面使用 kill 命令來終止阻塞狀態外,還能夠直接在該會話中使用 「 ctrl+c 」 來停止阻塞,這又是什麼原理呢?

 答:首先要知道客戶端操做服務端是客戶端開啓一個線程,讓這個線程去處理,發送請求數據,經過網絡傳輸到服務端,服務端再分配線程去處理。而 "ctrl +c " 是讓客戶端另開一個鏈接,併發送一個 kill query 的命令。因此雖然咱們看來是中斷了阻塞,可是處理上一個鏈接的服務端線程並必定就會被中斷。

二、爲何在指定庫名鏈接時會很慢?以下圖:

答:這是因爲 MySQL 默認開啓了自動補全功能(輸入表名時可使用 tab 自動補全)。其實現是在鏈接數據庫多執行一些操做:

一、執行 show databases;
二、切到 db1 庫,執行 show tables;
三、把這兩個命令的結果用於構建一個本地的哈希表。(最耗時)

這個功能能夠在命令中加上 -A 關閉。同時使用 -quick 也能夠關閉。可是使用 -quick 可能會使客戶端性能下降。這是爲何?這就要說到數據在服務器端與客戶端發送的流程了。

 

服務器線程執行流程

客戶端首先與服務器端驗證用戶名和密碼,經過後正式創建鏈接,而後客戶端發送請求,服務器端從線程池中取一個線程來處理。處理的過程:

一、獲取一行,寫到 net_buffer 中。這塊內存的大小是由參數 net_buffer_length 定義的,默認是 16k。
二、重複獲取行,直到 net_buffer 寫滿,調用網絡接口發出去。
三、若是發送成功,就清空 net_buffer,而後繼續取下一行,並寫入 net_buffer。
四、若是發送函數返回 EAGAIN 或 WSAEWOULDBLOCK,就表示本地網絡棧(socket send buffer)寫滿了,進入等待。直到網絡棧從新可寫,再繼續發送。

從上面的流程能夠知道,若是一次要發送的數據量超過 socket send buffer 空間,那麼就會拆分開來發送,並不會發生 " 內存打爆 " 的狀況。由此咱們能夠知道,MySQL 是邊讀邊發的

 

一、若是請求返回的數據量很大,那麼在等待返回的過程當中使用 show processlist 查看 State 列的值就會爲 " Sending to client",表示服務器端的網絡棧寫滿了。

這是由於 Sate 列值的變化是在查詢請求到達開始執行就會變爲 " Sending data ",若是網絡棧寫滿發就會切換爲 " Sending to client ",表示 " 正在等待客戶端接收結果 "。" Sending data " 可能處於線程執行過程當中的任意階段,好比由於鎖而阻塞的場景。

 

二、若是 show processlist 的 State 列一直爲 " Sending to Client ",那麼能夠

  1)查看這條SQL,判斷是否能夠優化,減小返回值。

  2)將 net_buffer_length 設的大一些,來避免或者減小發送阻塞的時間。

 

客戶端執行流程

在開始客戶端會建立線程去鏈接服務器端,而後接收服務端返回的數據,客戶端接收服務器端返回的數據有兩種方式:

一、本地緩存。在本地開一片內存,先把結果存起來。若是用 API 開發,對應的就是 mysql_store_result 方法。建議在客戶端處理量大時使用本地緩存。可使用 mysql -h$host -P$port -u$user -p$pwd -e "select * from db1.t" > $target_file 將返回的數據保存到指定文件。

二、不緩存,讀一個處理一個。若是用 API 開發,對應的就是 mysql_use_result 方法。

 

回到上面的問題,爲何使用 -quick 可能會致使客戶端性能降低?這是由於客戶端默認使用緩存來接收,因此在客戶端正在處理其餘數據時就能夠先進行緩存,等到後面直接讀取緩存就能夠了。而使用 quick 就會使客戶端接收不使用緩存,那麼若是客戶端正在執行其餘操做這個數據就會被阻塞,而且服務器端對應的線程也會由於沒有收到客戶端的反饋而沒有中斷此次事務,此次事務涉及到的資源鎖也沒有釋放,形成併發問題,影響效率。除此以外, quick 還有三個效果。

一、就是前面提到的,跳過表名自動補全功能。
二、客戶端接收數據使用不緩存的方式。而 mysql_store_result 方法須要申請本地內存來緩存查詢結果,若是查詢結果太大,會耗費較多的本地內存,可能會影響客戶端本地機器的性能;
三、不會把執行命令記錄到本地的命令歷史文件。

相關文章
相關標籤/搜索