共計 4176 個字符,預(yù)計需要花費 11 分鐘才能閱讀完成。
自動寫代碼機器人,免費開通
丸趣 TV 小編給大家分享一下 MySQL 怎么一個殺掉數(shù)據(jù)庫空閑事務(wù),希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!
我們經(jīng)常遇到一個情況,就是網(wǎng)絡(luò)斷開或程序 Bug 導(dǎo)致 COMMIT/ROLLBACK 語句沒有傳到數(shù)據(jù)庫,也沒有釋放線程,但是線上事務(wù)鎖定等待嚴(yán)重,連接數(shù)暴漲,尤其在測試庫這種情況很多,線上也偶有發(fā)生,于是想為 MySQL 增加一個殺掉空閑事務(wù)的功能。下面丸趣 TV 丸趣 TV 小編來講解下 MySQ 如何一個殺掉數(shù)據(jù)庫空閑事務(wù)?
MySQ 如何一個殺掉數(shù)據(jù)庫空閑事務(wù)
通過 MySQL Server 層有很多不確定因素,最保險還是在存儲引擎層實現(xiàn),我們用的幾乎都是 InnoDB/XtraDB,所以就基于 Percona 來修改了,Oracle 版的 MySQL 也可以照著修改。
需求:
1. 一個事務(wù)啟動,如果事務(wù)內(nèi)最后一個語句執(zhí)行完超過一個時間 (innodb_idle_trx_timeout),就應(yīng)該關(guān)閉鏈接。
2. 如果事務(wù)是純讀事務(wù),因為不加鎖,所以無害,不需要關(guān)閉,保持即可。
雖然這個思路被 Percona 的指出 Alexey Kopytov 可能存在“Even though SELECT queries do not place row locks by default (there are exceptions), they can still block undo log records from being purged.”的問題,但是我們確實有場景 SELECT 是絕對不能 kill 的,除非之后的 INSERT/UPDATE/DELETE 發(fā)生了,所以我根據(jù) 我們的業(yè)務(wù)特點來修改。
跟 Percona 的 Yasufumi Kinoshita 和 Alexey Kopytov 提出過純 SELECT 事務(wù)不應(yīng)被 kill,但通過一個參數(shù)控制的方案還沒有被 Alexey Kopytov 接受,作為通用處理我提出了用兩個變量分別控制純讀事務(wù)的空閑超時時間和有鎖事務(wù)的空閑超時時間,還在等待 Percona 的回復(fù),因為這個 方案還在測試,就先不開放修改了,當(dāng)然如果你很熟悉 MYSQL 源碼,我提出這個思路你肯定知道怎么分成這兩個參數(shù)控制了。
根據(jù)這兩個需 求我們來設(shè)計方法,首先想到這個功能肯定是放在 InnoDB Master Thread 最方便,Master Thread 每秒調(diào)度一次,可以順便檢查空閑事務(wù),然后關(guān)閉,因為在事務(wù)中操作 trx- mysql_thd 并不安全,所以一般來說最好在 InnoDB 層換成 Thread ID 操作,并且 InnoDB 中除了 ha_innodb.cc,其他地方不能飲用 THD,所以 Master Thread 中需要的線程數(shù)值,都需要在 ha_innodb 中計算好傳遞整型或布爾型返回值給 master thread 調(diào)用。
首先,我們要增加一個參數(shù):idle_trx_timeout,它表示事務(wù)多久沒有下一條語句發(fā)生就超時關(guān)閉。
在 storage/innodb_plugin/srv/srv0srv.c 的“/* plugin options */”注釋下增加如下代碼注冊 idle_trx_timeout 變量。
if (srv_idle_trx_timeout trx_sys) {
trx_t* trx;
time_t now;
rescan_idle:
now = time(NULL);
mutex_enter(kernel_mutex);
trx = UT_LIST_GET_FIRST(trx_sys- mysql_trx_list); # 從當(dāng)前事務(wù)列表里獲取第一個事務(wù)
while (trx) {# 依次循環(huán)每個事務(wù)進行檢查
if (trx- conc_state == TRX_ACTIVE
trx- mysql_thd
innobase_thd_is_idle(trx- mysql_thd)) {# 如果事務(wù)還活著并且它的狀態(tài)時空閑的
ib_int64_t start_time = innobase_thd_get_start_time(trx- mysql_thd); # 獲取線程最后一個語句的開始時間
ulong thd_id = innobase_thd_get_thread_id(trx- mysql_thd); #獲取線程 ID,因為存儲引擎內(nèi)直接操作 THD 不安全
if (trx- last_stmt_start != start_time) {# 如果事務(wù)最后語句起始時間不等于線程最后語句起始時間說明事務(wù)是新起的
trx- idle_start = now; # 更新事務(wù)的空閑起始時間
trx- last_stmt_start = start_time; # 更新事務(wù)的最后語句起始時間
} else if (difftime(now, trx- idle_start) # 如果事務(wù)不是新起的,已經(jīng)執(zhí)行了一部分則判斷空閑時間有多長了
srv_idle_trx_timeout) {# 如果空閑時間超過閾值則殺掉鏈接
/* kill the session */
mutex_exit(kernel_mutex);
thd_kill(thd_id); # 殺鏈接
goto rescan_idle;
}
}
trx = UT_LIST_GET_NEXT(mysql_trx_list, trx); # 檢查下一個事務(wù)
}
mutex_exit(kernel_mutex);
}
代碼往下找在 innobase_system_variables 結(jié)構(gòu)體內(nèi)加上:
MySQ 如何一個殺掉數(shù)據(jù)庫空閑事務(wù)
if (srv_idle_trx_timeout trx_sys) {
trx_t* trx;
time_t now;
rescan_idle:
now = time(NULL);
mutex_enter(kernel_mutex);
trx = UT_LIST_GET_FIRST(trx_sys- mysql_trx_list); # 從當(dāng)前事務(wù)列表里獲取第一個事務(wù)
while (trx) {# 依次循環(huán)每個事務(wù)進行檢查
if (trx- conc_state == TRX_ACTIVE
trx- mysql_thd
innobase_thd_is_idle(trx- mysql_thd)) {# 如果事務(wù)還活著并且它的狀態(tài)時空閑的
ib_int64_t start_time = innobase_thd_get_start_time(trx- mysql_thd); # 獲取線程最后一個語句的開始時間
ulong thd_id = innobase_thd_get_thread_id(trx- mysql_thd); #獲取線程 ID,因為存儲引擎內(nèi)直接操作 THD 不安全
if (trx- last_stmt_start != start_time) {# 如果事務(wù)最后語句起始時間不等于線程最后語句起始時間說明事務(wù)是新起的
trx- idle_start = now; # 更新事務(wù)的空閑起始時間
trx- last_stmt_start = start_time; # 更新事務(wù)的最后語句起始時間
} else if (difftime(now, trx- idle_start) # 如果事務(wù)不是新起的,已經(jīng)執(zhí)行了一部分則判斷空閑時間有多長了
srv_idle_trx_timeout) {# 如果空閑時間超過閾值則殺掉鏈接
/* kill the session */
mutex_exit(kernel_mutex);
thd_kill(thd_id); # 殺鏈接
goto rescan_idle;
}
}
trx = UT_LIST_GET_NEXT(mysql_trx_list, trx); # 檢查下一個事務(wù)
}
mutex_exit(kernel_mutex);
}
有了這個變量,我們需要在 Master Thread(storage/innodb_plugin/srv/srv0srv.c) 中執(zhí)行檢測函數(shù)查找空閑事務(wù)。在 loop 循環(huán)的 if (sync_array_print_long_waits( waiter, sema) 判斷后加上這段判斷
if (srv_idle_trx_timeout trx_sys) {
trx_t* trx;
time_t now;
rescan_idle:
now = time(NULL);
mutex_enter(kernel_mutex);
trx = UT_LIST_GET_FIRST(trx_sys- mysql_trx_list); # 從當(dāng)前事務(wù)列表里獲取第一個事務(wù)
while (trx) {# 依次循環(huán)每個事務(wù)進行檢查
if (trx- conc_state == TRX_ACTIVE
trx- mysql_thd
innobase_thd_is_idle(trx- mysql_thd)) {# 如果事務(wù)還活著并且它的狀態(tài)時空閑的
ib_int64_t start_time = innobase_thd_get_start_time(trx- mysql_thd); # 獲取線程最后一個語句的開始時間
ulong thd_id = innobase_thd_get_thread_id(trx- mysql_thd); #獲取線程 ID,因為存儲引擎內(nèi)直接操作 THD 不安全
if (trx- last_stmt_start != start_time) {# 如果事務(wù)最后語句起始時間不等于線程最后語句起始時間說明事務(wù)是新起的
trx- idle_start = now; # 更新事務(wù)的空閑起始時間
trx- last_stmt_start = start_time; # 更新事務(wù)的最后語句起始時間
} else if (difftime(now, trx- idle_start) # 如果事務(wù)不是新起的,已經(jīng)執(zhí)行了一部分則判斷空閑時間有多長了
srv_idle_trx_timeout) {# 如果空閑時間超過閾值則殺掉鏈接
/* kill the session */
mutex_exit(kernel_mutex);
thd_kill(thd_id); # 殺鏈接
goto rescan_idle;
}
}
trx = UT_LIST_GET_NEXT(mysql_trx_list, trx); # 檢查下一個事務(wù)
}
mutex_exit(kernel_mutex);
}
其中 trx 中的變量是新加的,在 storage/innodb_plugin/include/trx0trx.h 的 trx_truct 加上需要的變量。
看完了這篇文章,相信你對“MySQL 怎么一個殺掉數(shù)據(jù)庫空閑事務(wù)”有了一定的了解,如果想了解更多相關(guān)知識,歡迎關(guān)注丸趣 TV 行業(yè)資訊頻道,感謝各位的閱讀!
向 AI 問一下細(xì)節(jié)