自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

MySQL 客戶端 Ctrl + C,服務端會發(fā)生什么?

數(shù)據(jù)庫 MySQL
Ctrl + C 之后,客戶端會干什么,服務端又會發(fā)生什么?我們一起來看看。

我們也許有過這樣的經(jīng)歷:用 mysql? 客戶端連上數(shù)據(jù)庫,執(zhí)行一條 SQL,結(jié)果遲遲執(zhí)行不完,我們等得不耐煩了,順手就是一個 Ctrl + C。

Ctrl + C 之后,客戶端會干什么,服務端又會發(fā)生什么?我們一起來看看。

本文內(nèi)容基于 MySQL 8.0.32 源碼,涉及存儲引擎為 InnoDB。

1、客戶端會干什么?

想要觀察 Ctrl + C 時,客戶端會干什么,用 mysql 連接數(shù)據(jù)庫時可以指定 -v 參數(shù),如下:

mysql -h127.0.0.1 -uroot -v

連上數(shù)據(jù)庫之后,執(zhí)行一條 SQL(以 UPDATE 為例?)。SQL 執(zhí)行完成之前,在鍵盤上按下 Ctrl + C,如下:

注意:沒有使用 begin 顯式開啟事務,且系統(tǒng)變量 autocommit 的值為 ON。

mysql> UPDATE t1 SET blob1 = REPEAT("這是 blob2 字段", 10240);
--------------
UPDATE t1 SET blob1 = REPEAT("這是 blob2 字段", 10240)
--------------

-- 客戶端發(fā)送 KILL QUERY 給服務端之后
-- 輸出的提示信息
^C^C -- sending "KILL QUERY 11" to server ...

# 服務端執(zhí)行 KILL QUERY 之后
# 客戶端自己的輸出信息
^C -- query aborted

-- 服務端返回給客戶端的信息
ERROR 1317 (70100): Query execution was interrupted

從以上輸出可以看到,客戶端 Ctrl + C,實際上是給服務端發(fā)出了一條 KILL QUERY 命令。

這和我們手動執(zhí)行 KILL QUERY 命令是一樣的,接下來,我們就來看看服務端是怎么執(zhí)行 KILL QUERY 命令的。

2、KILL QUERY

在 KILL QUERY 命令之前,客戶端已經(jīng)發(fā)出了一條 Update SQL,服務端分配了一個線程,正在執(zhí)行 Update SQL。

Update SQL 還沒執(zhí)行完,客戶端 Ctrl + C 又發(fā)出了 KILL QUERY 命令,服務端收到命令之后,會調(diào)度另一個線程來執(zhí)行 KILL QUERY 命令。

為了方便介紹,我們把執(zhí)行 Update SQL 的線程稱為 Update 線程?,執(zhí)行 KILL QUERY 命令的線程稱為 Kill 線程?。注意:MySQL 內(nèi)部是不做這樣區(qū)分的。

KILL QUERY 命令的執(zhí)行流程如下:

第 1 步,Kill 線程根據(jù) query id? 查找 Update 線程。如果沒有找到?,KILL QUERY 命令執(zhí)行結(jié)束;如果找到了,進入第 2 步。

query id? 是 show processlist 執(zhí)行結(jié)果中的 id 字段。

第 2 步,Kill 線程判斷當前連接的 MySQL 用戶是否有權(quán)限干掉 Update 線程。如果沒有?權(quán)限,KILL QUERY 命令執(zhí)行結(jié)束;如果有權(quán)限,進入第 3 步。

第 3 步,判斷 Update 線程是否正在讀寫數(shù)據(jù)字典表。

如果不是?,Kill 線程繼續(xù)執(zhí)行第 4 ~ 6 步;如果是,Kill 線程的使命就到此結(jié)束了,接力棒交給 Update 線程。

Update 線程?讀寫數(shù)據(jù)字典表結(jié)束,就會馬上開始執(zhí)行 KILL QUERY 命令的第 3 ~ 6 步。

這種情況下,第 3 步會被執(zhí)行 2 次(Kill 線程和 Update 線程各執(zhí)行一次)。

第 4 步,把 Update 線程的 killed? 屬性設置為 KILL_QUERY?,此時,Update 線程處于被標記為將要被干掉,但是還沒有被干掉的狀態(tài)。

這一步可以想象成城市建設過程中,在要拆遷的房子上寫了個大大的拆字,但是房子還立在那里。

第 5 步,如果 Update 線程正在等待獲取存儲引擎中的鎖,則放棄等待;如果 Update 線程已經(jīng)持有存儲引擎中的鎖,則釋放鎖。

第 6 步,判斷 Update 線程是否持有某個條件變量?(保存在 current_cond)中。

如果持有,發(fā)送廣播通知正在等待這個條件變量的其它線程,告訴它們可以繼續(xù)執(zhí)行了。

通過前面的介紹,我們可以看到:不管是 Kill 線程,還是 Update 線程自己執(zhí)行?第 3 ~ 6 步?,都只是給 Update 線程打上了 KILL_QUERY 標記,而沒有直接把 Update 線程干掉。

Update 線程是怎么被干掉的呢?請繼續(xù)往下看。

3、自己把自己干掉

KILL QUERY 執(zhí)行過程中,為什么不直接把 Update 線程干掉?

不是不想,而是不能。

因為線程不管執(zhí)行什么操作,都需要進行收尾工作,做到有始有終。

如果 Update 線程直接被干掉,就來不及進行收尾工作,例如:已經(jīng)申請的內(nèi)存無法釋放,會導致內(nèi)存泄漏。

所以,想要妥善干掉一個線程,需要即將被干掉的線程主動配合 Kill 線程才行。

妥善干掉一個 Update 線程的場景是這樣的:

Kill 線程對 Update 線程說:我要把你干掉。

Update 線程回答:不勞你動手,我自己來。

MySQL 讓這個場景變成現(xiàn)實的方式,是在代碼中的各個角落進行埋點,埋點邏輯:判斷當前線程是否被打上了 KILL_QUERY 標記,如果?是,則中斷正在執(zhí)行的操作,進入收尾階段。

舉個例子:

// sql/sql_update.cc
// 以下代碼處理更新單表的 SQL,例如:
// update t1 set i1 = 100
bool Sql_cmd_update::update_single_table(THD *thd) {
...
while (true) {
// 從存儲引擎讀取一條記錄
error = iterator->Read();
// 如果讀取出錯(error)
// 或者 thd->killed 不等于 0(也就是 true)
// 對應本文的場景是:線程被打上了 KILL_QUERY 標記
// 直接結(jié)束循環(huán)
if (error || thd->killed) break;
...
}
...
}

從以上代碼可以看到,執(zhí)行 Update 操作過程中,如果發(fā)現(xiàn)讀取出錯(對應本文場景是 Update 線程被打上了 KILL_QUERY 標記),直接 break 退出循環(huán),中斷執(zhí)行。

4、回滾

Update 線程執(zhí)行過程中,事務有可能已經(jīng)增、刪、改了一些數(shù)據(jù),中斷正在執(zhí)行的操作之后,事務是需要回滾的。

當 Update 線程的執(zhí)行流程回到 mysql_execute_command():

int mysql_execute_command(THD *thd, bool first_level) {
...
if ((thd->is_error() && !early_error_on_rep_command) ||
(thd->variables.option_bits & OPTION_MASTER_SQL_ERROR))
trans_/opt/data/workspace_c/mysql8/sql/sql_class.ccrollback_stmt(thd);
else {
/* If commit fails, we should be able to reset the OK status. */
thd->get_stmt_da()->set_overwrite_status(true);
trans_commit_stmt(thd);
thd->get_stmt_da()->set_overwrite_status(false);
}
...
}

從代碼中可以看到,thd->is_error() 返回 true,說明事務執(zhí)行過程中出現(xiàn)了錯誤,對應到本文的場景,就是事務被 KILL QUERY 中斷了,會執(zhí)行 trans_rollback_stmt(thd),回滾事務。

只有在開啟組復制(GROUP REPLICATION)過程中出現(xiàn)錯誤時,early_error_on_rep_command 才有可能被設置為 true,這里我們先忽略。

到這里,KILL QUERY 就算是基本介紹完了。

之所以說基本介紹完了,是因為還留有一點點尾巴。

前面我們介紹過,Update 線程執(zhí)行到埋點的時候,如果判斷自己已經(jīng)被標記為即將被干掉,就會中斷執(zhí)行。

但是,還有一種很小的可能性,就是 Update 線程執(zhí)行過程中,已經(jīng)經(jīng)過了所有埋點之后,才被標記為即將被干掉,Update 線程也就沒有機會中斷執(zhí)行了。

這種情況下,就會進入以上代碼中的 else 分支,執(zhí)行 trans_commit_stmt(thd),提交事務。

鑒于進入 else 分支提交事務的可能性很小,我們可以認為只要客戶端 Ctrl + C,Update 線程就會中斷執(zhí)行,并回滾事務。

5、總結(jié)

客戶端連接上 MySQL 之后,給服務端發(fā)送一條 SQL,SQL 執(zhí)行完成之前,客戶端 Ctrl + C,實際上會給服務端發(fā)送一條 KILL QUERY 命令,和我們手動執(zhí)行 kill query <query_id> 的效果是一樣的。

服務端會分配一個空閑線程(Kill 線程)執(zhí)行 kill query 操作,給 Update 線程打上 KILL_QUERY 標記。

如果即將被干掉的線程(Update 線程)正在讀寫數(shù)據(jù)字典表,它會從 kill 線程手上接過接力棒,給自己打上 KILL_QUERY 標記。

Update 線程發(fā)現(xiàn)自己被打上了 KILL_QUERY 標記,就會中斷執(zhí)行,在 mysql_execute_command() 方法中,會回滾事務。

有一點需要說明,前面只是以 Update SQL 為例來介紹 KILL QUERY,其它 SQL 的 KILL QUERY 流程也是一樣的。?

6、番外篇

前面 1 ~ 5 小節(jié)介紹的是沒有通過 begin 語句顯式開啟事務,并且系統(tǒng)變量 autocommit 的值是 ON 的場景。

如果通過 begin 顯式開啟了事務,或者把系統(tǒng)變量 autocommit 的值設置為 OFF,前面 1 ~ 5 小節(jié)介紹的內(nèi)容也是適用的,但是會有一點區(qū)別:

4.回滾小節(jié)只能作用于事務中的一條 SQL,而不會影響整個事務。至于整個事務是提交還是回滾,取決于我們會給服務端發(fā)送 commit 還是 rollback 語句。

本文轉(zhuǎn)載自微信公眾號「一樹一溪」,可以通過以下二維碼關注。轉(zhuǎn)載本文請聯(lián)系一樹一溪公眾號。

責任編輯:姜華 來源: 一樹一溪
相關推薦

2009-08-21 15:59:22

服務端與客戶端通信

2009-08-21 16:14:52

服務端與客戶端通信

2009-08-21 15:36:41

服務端與客戶端

2009-08-21 15:54:40

服務端與客戶端

2023-04-03 08:13:05

MySQLCtrl + C

2011-09-09 09:44:23

WCF

2010-03-18 17:47:07

Java 多客戶端通信

2024-03-06 14:58:52

客戶端微服務架構(gòu)

2010-11-19 14:22:04

oracle服務端

2015-01-13 10:32:23

RestfulWeb框架

2021-06-11 06:54:34

Dubbo客戶端服務端

2021-10-19 08:58:48

Java 語言 Java 基礎

2010-05-28 14:11:37

SVN1.6

2022-09-05 14:36:26

服務端TCP連接

2012-03-02 10:38:33

MySQL

2011-08-17 10:10:59

2022-01-05 08:03:23

C#通信Rest

2021-09-22 15:46:29

虛擬桌面瘦客戶端胖客戶端

2009-08-06 17:12:13

C# WebServi

2021-10-14 08:39:17

Java Netty Java 基礎
點贊
收藏

51CTO技術(shù)棧公眾號