新聞中心
mysql數(shù)據(jù)庫表鎖等待超時(shí)怎么解決
當(dāng)你開始執(zhí)行一個(gè) ALTER ,而你遇到了可怕的“元數(shù)據(jù)鎖定等待”,我敢肯定你一定遇見過。我最近遇到了一個(gè)案例,其中被更改的表要執(zhí)行一個(gè)很小范圍的更新(100行)。ALTER 在負(fù)載測試期間一直等待了幾個(gè)小時(shí)。在停止負(fù)載測試后,ALTER 按預(yù)期在不到一秒的時(shí)間內(nèi)就完成了。那么這里發(fā)生了什么?
創(chuàng)新互聯(lián)建站是一家專注于網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)與策劃設(shè)計(jì),漢中網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)建站做網(wǎng)站,專注于網(wǎng)站建設(shè)十載,網(wǎng)設(shè)計(jì)領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:漢中等地區(qū)。漢中做網(wǎng)站價(jià)格咨詢:18982081108
檢查外鍵
每當(dāng)有奇數(shù)次鎖定時(shí),我的第一直覺就是檢查外鍵。當(dāng)然這張表有一些外鍵引用了一個(gè)更繁忙的表。但是這種行為似乎仍然很奇怪。對表運(yùn)行 ALTER 時(shí),會(huì)針對子表請求一個(gè) SHARED_UPGRADEABLE 元數(shù)據(jù)鎖。還有針對父級的 SHARED_READ_ONLY 元數(shù)據(jù)鎖。
我們來看看如何根據(jù)文檔獲取元數(shù)據(jù)鎖定[1]:
如果給定鎖定有多個(gè)服務(wù)器,則首先滿足最高優(yōu)先級鎖定請求,并且與 max_write_lock_count系統(tǒng)變量有關(guān)。寫鎖定請求的優(yōu)先級高于讀取鎖定請求。
[1]:
請務(wù)必注意鎖定順序是序列化的:語句逐個(gè)獲取元數(shù)據(jù)鎖,而不是同時(shí)獲取,并在此過程中執(zhí)行死鎖檢測。
通常在考慮隊(duì)列時(shí)考慮先進(jìn)先出。如果我發(fā)出以下三個(gè)語句(按此順序),它們將按以下順序完成:
1. INSERT INTO parent2. ALTER TABLE child3. INSERT INTO parent
但是當(dāng)子 ALTER 語句請求對父進(jìn)行讀取鎖定時(shí),盡管排序,但兩個(gè)插入將在 ALTER 之前完成。以下是可以演示此示例的示例場景:
數(shù)據(jù)初始化:
CREATE TABLE `parent` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`val` varchar(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
CREATE TABLE `child` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parent_id` int(11) DEFAULT NULL,
`val` varchar(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_parent` (`parent_id`),
CONSTRAINT `fk_parent` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB;
INSERT INTO `parent` VALUES (1, "one"), (2, "two"), (3, "three"), (4, "four");
Session 1:
start transaction;update parent set val = "four-new" where id = 4;
Session 2:
alter table child add index `idx_new` (val);
Session 3:
start transaction;update parent set val = "three-new" where id = 3;
此時(shí),會(huì)話 1 具有打開的事務(wù),并且處于休眠狀態(tài),并在父級上授予寫入元數(shù)據(jù)鎖定。 會(huì)話 2 具有在子級上授予的可升級(寫入)鎖定,并且正在等待父級的讀取鎖定。最后會(huì)話 3 具有針對父級的授權(quán)寫入鎖定:
mysql select * from performance_schema.metadata_locks;+-------------+-------------+-------------------+---------------+-------------+| OBJECT_TYPE | OBJECT_NAME | LOCK_TYPE ? ? ? ? | LOCK_DURATION | LOCK_STATUS |+-------------+-------------+-------------------+---------------+-------------+| TABLE ? ? ? | child ? ? ? | SHARED_UPGRADABLE | TRANSACTION ? | GRANTED ? ? | - ALTER (S2)| TABLE ? ? ? | parent ? ? ?| SHARED_WRITE ? ? ?| TRANSACTION ? | GRANTED ? ? | - UPDATE (S1)| TABLE ? ? ? | parent ? ? ?| SHARED_WRITE ? ? ?| TRANSACTION ? | GRANTED ? ? | - UPDATE (S3)| TABLE ? ? ? | parent ? ? ?| SHARED_READ_ONLY ?| STATEMENT ? ? | PENDING ? ? | - ALTER (S2)+-------------+-------------+-------------------+---------------+-------------+
請注意,具有掛起鎖定狀態(tài)的唯一會(huì)話是會(huì)話 2(ALTER)。會(huì)話 1 和會(huì)話 3 (分別在 ALTER 之前和之后發(fā)布)都被授予了寫鎖。排序失敗的地方是在會(huì)話 1 上發(fā)生提交的時(shí)候。在考慮有序隊(duì)列時(shí),人們會(huì)期望會(huì)話 2 獲得鎖定,事情就會(huì)繼續(xù)進(jìn)行。但是,由于元數(shù)據(jù)鎖定系統(tǒng)的優(yōu)先級性質(zhì),會(huì)話 3 具有鎖定,會(huì)話 2 仍然等待。
如果另一個(gè)寫入會(huì)話進(jìn)入并啟動(dòng)新事務(wù)并獲取針對父表的寫鎖定,則即使會(huì)話 3 完成,ALTER 仍將被阻止。
只要我保持一個(gè)對父表打開元數(shù)據(jù)鎖定的活動(dòng)事務(wù),子表上的 ALTER 將永遠(yuǎn)不會(huì)完成。更糟糕的是,由于子表上的寫鎖定成功(但是完整語句正在等待獲取父讀鎖定),所以針對子表的所有傳入讀取請求都將被阻止!
另外,請考慮一下您通常如何對無法完成的語句進(jìn)行故障排除。您查看已經(jīng)打開較長時(shí)間的事務(wù)(在進(jìn)程列表和 InnoDB 狀態(tài)中)。但由于阻塞線程現(xiàn)在比 ALTER 線程更年輕,因此您將看到的最舊的事務(wù)/線程是 ALTER 。
這正是這種情況下發(fā)生的情況。在準(zhǔn)備發(fā)布時(shí),我們的客戶端正在運(yùn)行 ALTER 語句并結(jié)合負(fù)載測試(一種非常好的做法!)以確保順利發(fā)布。問題是負(fù)載測試保持對父表打開一個(gè)活動(dòng)的寫事務(wù)。這并不是說它只是一直在寫,而是有多個(gè)線程,一個(gè)總是活躍的。 這阻止了 ALTER 完成并阻止對相對靜態(tài)的子表的隨后的讀請求。
幸運(yùn)的是,這個(gè)問題有一個(gè)解決方案(除了從設(shè)計(jì)模式中驅(qū)逐外鍵)。變量?max_write_lock_count[2]?可用于允許在寫入鎖定之后在讀取鎖定之前授予讀取鎖定連續(xù)寫鎖。默認(rèn)情況下,此變量設(shè)置為 18446744073709551615,如果你對該表發(fā)出 10,000 次寫入/秒,那么你的讀將被鎖定 5800 萬年……
MySQL數(shù)據(jù)庫表被鎖、解鎖,刪除事務(wù)
在程序員的職業(yè)生涯中,總會(huì)遇到數(shù)據(jù)庫表被鎖的情況,前些天就又撞見一次。由于業(yè)務(wù)突發(fā)需求,各個(gè)部門都在批量操作、導(dǎo)出數(shù)據(jù),而數(shù)據(jù)庫又未做讀寫分離,結(jié)果就是:數(shù)據(jù)庫的某張表被鎖了!
用戶反饋系統(tǒng)部分功能無法使用,緊急排查,定位是數(shù)據(jù)庫表被鎖,然后進(jìn)行緊急處理。這篇文章給大家講講遇到類似緊急狀況的排查及解決過程,建議點(diǎn)贊收藏,以備不時(shí)之需。
用戶反饋某功能頁面報(bào)502錯(cuò)誤,于是第一時(shí)間看服務(wù)是否正常,數(shù)據(jù)庫是否正常。在控制臺(tái)看到數(shù)據(jù)庫CPU飆升,堆積大量未提交事務(wù),部分事務(wù)已經(jīng)阻塞了很長時(shí)間,基本定位是數(shù)據(jù)庫層出現(xiàn)問題了。
查看阻塞事務(wù)列表,發(fā)現(xiàn)其中有鎖表現(xiàn)象,本想利用控制臺(tái)直接結(jié)束掉阻塞的事務(wù),但控制臺(tái)賬號(hào)權(quán)限有限,于是通過客戶端登錄對應(yīng)賬號(hào)將鎖表事務(wù)kill掉,才避免了情況惡化。
下面就聊聊,如果當(dāng)突然面對類似的情況,我們該如何緊急響應(yīng)?
想象一個(gè)場景,當(dāng)然也是軟件工程師職業(yè)生涯中會(huì)遇到的一種場景:原本運(yùn)行正常的程序,某一天突然數(shù)據(jù)庫的表被鎖了,業(yè)務(wù)無法正常運(yùn)轉(zhuǎn),那么我們該如何快速定位是哪個(gè)事務(wù)鎖了表,如何結(jié)束對應(yīng)的事物?
首先最簡單粗暴的方式就是:重啟MySQL。對的,網(wǎng)管解決問題的神器——“重啟”。至于后果如何,你能不能跑了,要你自己三思而后行了!
重啟是可以解決表被鎖的問題的,但針對線上業(yè)務(wù)很顯然不太具有可行性。
下面來看看不用跑路的解決方案:
遇到數(shù)據(jù)庫阻塞問題,首先要查詢一下表是否在使用。
如果查詢結(jié)果為空,那么說明表沒在使用,說明不是鎖表的問題。
如果查詢結(jié)果不為空,比如出現(xiàn)如下結(jié)果:
則說明表(test)正在被使用,此時(shí)需要進(jìn)一步排查。
查看數(shù)據(jù)庫當(dāng)前的進(jìn)程,看看是否有慢SQL或被阻塞的線程。
執(zhí)行命令:
該命令只顯示當(dāng)前用戶正在運(yùn)行的線程,當(dāng)然,如果是root用戶是能看到所有的。
在上述實(shí)踐中,阿里云控制臺(tái)之所以能夠查看到所有的線程,猜測應(yīng)該使用的就是root用戶,而筆者去kill的時(shí)候,無法kill掉,是因?yàn)榈卿浀挠脩舴莚oot的數(shù)據(jù)庫賬號(hào),無法操作另外一個(gè)用戶的線程。
如果情況緊急,此步驟可以跳過,主要用來查看核對:
如果情況緊急,此步驟可以跳過,主要用來查看核對:
看事務(wù)表INNODB_TRX中是否有正在鎖定的事務(wù)線程,看看ID是否在show processlist的sleep線程中。如果在,說明這個(gè)sleep的線程事務(wù)一直沒有commit或者rollback,而是卡住了,需要手動(dòng)kill掉。
搜索的結(jié)果中,如果在事務(wù)表發(fā)現(xiàn)了很多任務(wù),最好都kill掉。
執(zhí)行kill命令:
對應(yīng)的線程都執(zhí)行完kill命令之后,后續(xù)事務(wù)便可正常處理。
針對緊急情況,通常也會(huì)直接操作第一、第二、第六步。
這里再補(bǔ)充一些MySQL鎖相關(guān)的知識(shí)點(diǎn):數(shù)據(jù)庫鎖設(shè)計(jì)的初衷是處理并發(fā)問題,作為多用戶共享的資源,當(dāng)出現(xiàn)并發(fā)訪問的時(shí)候,數(shù)據(jù)庫需要合理地控制資源的訪問規(guī)則,而鎖就是用來實(shí)現(xiàn)這些訪問規(guī)則的重要數(shù)據(jù)結(jié)構(gòu)。
根據(jù)加鎖的范圍,MySQL里面的鎖大致可以分成全局鎖、表級鎖和行鎖三類。MySQL中表級別的鎖有兩種:一種是表鎖,一種是元數(shù)據(jù)鎖(metadata lock,MDL)。
表鎖是在Server層實(shí)現(xiàn)的,ALTER TABLE之類的語句會(huì)使用表鎖,忽略存儲(chǔ)引擎的鎖機(jī)制。表鎖通過lock tables… read/write來實(shí)現(xiàn),而對于InnoDB來說,一般會(huì)采用行級鎖。畢竟鎖住整張表影響范圍太大了。
另外一個(gè)表級鎖是MDL(metadata lock),用于并發(fā)情況下維護(hù)數(shù)據(jù)的一致性,保證讀寫的正確性,不需要顯式的使用,在訪問一張表時(shí)會(huì)被自動(dòng)加上。
常見的一種鎖表場景就是有事務(wù)操作處于:Waiting for table metadata lock狀態(tài)。
MySQL在進(jìn)行alter table等DDL操作時(shí),有時(shí)會(huì)出現(xiàn)Waiting for table metadata lock的等待場景。
一旦alter table TableA的操作停滯在Waiting for table metadata lock狀態(tài),后續(xù)對該表的任何操作(包括讀)都無法進(jìn)行,因?yàn)樗鼈円矔?huì)在Opening tables的階段進(jìn)入到Waiting for table metadata lock的鎖等待隊(duì)列。如果核心表出現(xiàn)了鎖等待隊(duì)列,就會(huì)造成災(zāi)難性的后果。
通過show processlist可以看到表上有正在進(jìn)行的操作(包括讀),此時(shí)alter table語句無法獲取到metadata 獨(dú)占鎖,會(huì)進(jìn)行等待。
通過show processlist看不到表上有任何操作,但實(shí)際上存在有未提交的事務(wù),可以在information_schema.innodb_trx中查看到。在事務(wù)沒有完成之前,表上的鎖不會(huì)釋放,alter table同樣獲取不到metadata的獨(dú)占鎖。
處理方法:通過 select * from information_schema.innodb_trxG, 找到未提交事物的sid,然后kill掉,讓其回滾。
通過show processlist看不到表上有任何操作,在information_schema.innodb_trx中也沒有任何進(jìn)行中的事務(wù)。很可能是因?yàn)樵谝粋€(gè)顯式的事務(wù)中,對表進(jìn)行了一個(gè)失敗的操作(比如查詢了一個(gè)不存在的字段),這時(shí)事務(wù)沒有開始,但是失敗語句獲取到的鎖依然有效,沒有釋放。從performance_schema.events_statements_current表中可以查到失敗的語句。
處理方法:通過performance_schema.events_statements_current找到其sid,kill 掉該session,也可以kill掉DDL所在的session。
總之,alter table的語句是很危險(xiǎn)的(核心是未提交事務(wù)或者長事務(wù)導(dǎo)致的),在操作之前要確認(rèn)對要操作的表沒有任何進(jìn)行中的操作、沒有未提交事務(wù)、也沒有顯式事務(wù)中的報(bào)錯(cuò)語句。
如果有alter table的維護(hù)任務(wù),在無人監(jiān)管的時(shí)候運(yùn)行,最好通過lock_wait_timeout設(shè)置好超時(shí)時(shí)間,避免長時(shí)間的metedata鎖等待。
關(guān)于MySQL的鎖表其實(shí)還有很多其他場景,我們在實(shí)踐的過程中盡量避免鎖表情況的發(fā)生,當(dāng)然這需要一定經(jīng)驗(yàn)的支撐。但更重要的是,如果發(fā)現(xiàn)鎖表我們要能夠快速的響應(yīng),快速的解決問題,避免影響正常業(yè)務(wù),避免情況進(jìn)一步惡化。所以,本文中的解決思路大家一定要收藏或記憶一下,做到有備無患,避免突然狀況下抓瞎。
詳解MySQL(InnoDB)如何處理死鎖
鎖是需要事務(wù)結(jié)束后才釋放的。
一個(gè)是 MVCC,一個(gè)是兩階段鎖協(xié)議。
為什么要并發(fā)控制呢?是因?yàn)槎鄠€(gè)用戶同時(shí)操作 MySQL 的時(shí)候,為了提高并發(fā)性能并且要求如同多個(gè)用戶的請求過來之后如同串行執(zhí)行的一樣(為了解決臟讀、不可重復(fù)讀、幻讀)
官方定義:
兩階段鎖協(xié)議是指所有事務(wù)必須分兩個(gè)階段對數(shù)據(jù)加鎖和解鎖,在對任何數(shù)據(jù)進(jìn)行讀、寫操作之前,事務(wù)首先要獲得對該數(shù)據(jù)的封鎖;在釋放一個(gè)封鎖之后,事務(wù)不再申請和獲得任何其他封鎖。
對應(yīng)到 MySQL 上分為兩個(gè)階段:
但是兩階段鎖協(xié)議不要求事務(wù)必須一次將所有需要使用的數(shù)據(jù)加鎖(innodb在需要的索引列數(shù)據(jù)才鎖行),并且在加鎖階段沒有順序要求,所以這種并發(fā)控制方式會(huì)形成死鎖。
MySQL有兩種死鎖處理方式:
死鎖檢測 (默認(rèn)開啟)
死鎖檢測的原理是構(gòu)建一個(gè)以事務(wù)為頂點(diǎn)、鎖為邊的有向圖,判斷有向圖是否存在環(huán),存在即有死鎖。
回滾
檢測到死鎖之后,選擇插入更新或者刪除的行數(shù)最少的事務(wù)回滾,基于 INFORMATION_SCHEMA.INNODB_TRX 表中的 trx_weight 字段來判斷。
收集死鎖信息:
減少死鎖:
死鎖解決:
MySQL 鎖等待超時(shí)(Lock wait timeout exceeded)
問題:Lock wait timeout exceeded; try restarting transaction
MySQL版本:5.6.44
官方文檔
意思是:InnoDB在鎖等待超時(shí)過期時(shí)報(bào)告此錯(cuò)誤。等待時(shí)間過長的語句被回滾(而不是整個(gè)事務(wù))。如果SQL語句需要等待其他事務(wù)完成的時(shí)間更長,則可以增加 innodb_lock_wait_timeout 配置選項(xiàng)的值;如果太多長時(shí)間運(yùn)行的事務(wù)導(dǎo)致鎖定問題并降低繁忙系統(tǒng)上的并發(fā)性,則可以減少該選項(xiàng)的值。
鎖等待超時(shí),可能是出現(xiàn)了死鎖,也可能有事務(wù)長時(shí)間未提交
庫:information_schema
表:
查看各表信息
innodb_trx 表
innodb_locks 表
innodb_lock_waits 表
processlist 表
模擬出現(xiàn)死鎖
準(zhǔn)備一張只有主鍵的表:t_test (id)
Navicat 新建查詢1
Navicat 新建查詢2
檢查是否鎖表
查詢當(dāng)前正在執(zhí)行的事務(wù)
查詢當(dāng)前出現(xiàn)的鎖
查詢鎖等待對應(yīng)的關(guān)系
查詢等待鎖的事務(wù)所執(zhí)行的SQL
最后,事務(wù)2 等待鎖超時(shí)報(bào)錯(cuò): Lock wait timeout exceeded; try restarting transaction;
通過事務(wù)線程ID查找進(jìn)程信息
win10 查看端口信息
記一次Mysql故障排查(lock wait timeout exceeded;try restarting transaction)
最近接了一個(gè)鍋,進(jìn)入新公司接手了一個(gè)進(jìn)入交付階段的項(xiàng)目.在code?review的時(shí)候發(fā)現(xiàn)很多問題,然后開始修復(fù)bug.
在測試階段突然發(fā)現(xiàn)幾乎所有涉及到更新的操作都失敗,下面貼出異常信息.
第一次 出現(xiàn)的時(shí)候百度了一下,猜想可能是多服務(wù)部署資源沖突,重啟服務(wù)故障消失.所以沒有特別重視
第二次 出現(xiàn)的時(shí)候只有測試環(huán)境部署,不存在多機(jī)資源沖突的問題,猜想是多線程資源交叉導(dǎo)致的,于是給可能導(dǎo)致資源競爭的地方加上了分布式鎖.
由于無法重現(xiàn)故障,所以并沒有確認(rèn)問題得到解決.
第三次 故障依舊,當(dāng)發(fā)現(xiàn)問題依然存在的時(shí)候,開始認(rèn)真反思,發(fā)現(xiàn)自己解決問題的思路明顯有問題,過于片面,一直都只在應(yīng)用層面尋求解決問題的辦法,而且解決問題的方式也只是在嘗試百度出來的方法.并沒有去思考更深層的問題.
在Mysql5.5中,information_schema 庫中增加了三個(gè)關(guān)于鎖的表(MEMORY引擎);
INNODB_TRX ## 當(dāng)前運(yùn)行的所有事務(wù)
INNODB _LOCKS ## 當(dāng)前出現(xiàn)的鎖
INNODB_LOCK_WAITS ## 鎖等待的對應(yīng)關(guān)系
通過查詢 INNODB_TRX 發(fā)現(xiàn)
當(dāng)前事務(wù)中又兩個(gè)RUNNING狀態(tài)開始時(shí)間在一個(gè)小時(shí)之前
開始一直以為是鎖表了
查看了 INNODB _LOCKS? 事務(wù)信息之后發(fā)現(xiàn)有4行數(shù)據(jù)被鎖住了一直沒有釋放
從這里開始發(fā)現(xiàn)問題了,應(yīng)用已經(jīng)拋了異常,事務(wù)理所當(dāng)然的應(yīng)該回滾才對,為什么資源依然沒有釋放,導(dǎo)致持續(xù)的阻塞呢?
其實(shí)最開始的異常信息就已經(jīng)給出了答案,回到開始的地方,再看異常信息就很清楚了,應(yīng)用里面的異常類是 MySQLTransactionRollBackException
是一個(gè)回滾異常, 這就說明在事務(wù)回滾的時(shí)候出了問題資源沒有得到釋放
然后開始查詢 MySQLTransactionRollBackException? 相關(guān)的信息
這個(gè)時(shí)候 innodb_rollback_on_timeout =0(默認(rèn)配置)這個(gè)MySQL的配置開始進(jìn)入我的視線,
舉個(gè)栗子
事務(wù)在鎖等待超時(shí)后是回滾事務(wù)內(nèi)所有的statement還是最后一條語句;
0表示rollback最后一條語句,默認(rèn)值; 有點(diǎn)坑爹啊( 細(xì)思極恐 )
1表示回滾事務(wù)內(nèi)所有的statements;(此參數(shù)是只讀參數(shù),需在my.cnf中配置,并且重啟生效;)
吃過一次虧,這次并沒有盲目的相信百度到的信息
于是開始測試
一、驗(yàn)證innodb_rollback_on_timeout=off的情況
1.session?A
開啟事務(wù),事務(wù)未提交,鎖住id=1的數(shù)據(jù)
2.session B?
開啟事務(wù),執(zhí)行更新id=2的數(shù)據(jù)成功(事務(wù)未提交,鎖住id=2),然后請求id=1等待鎖超時(shí),id=2的數(shù)據(jù)更改為222.
3.session C
請求id=2的數(shù)據(jù)50秒后顯示等待鎖超時(shí)
執(zhí)行 SELECT * FROM information_schema.INNODB_TRX;
可發(fā)現(xiàn)有資源一直未釋放,具體到測試數(shù)據(jù)中就是id=2的資源一直被鎖定,線程一直被掛起.
總結(jié):通過實(shí)驗(yàn)基本可以確定是業(yè)務(wù)資源交叉導(dǎo)致死鎖之后資源沒釋放造成的持續(xù)阻塞,
二.驗(yàn)證innodb_rollback_on_timeout=on
修改配置后將驗(yàn)證innodb_rollback_on_timeout=off的步驟再走一遍
發(fā)現(xiàn)鎖等待只能在業(yè)務(wù)層面盡量避免
on/off的區(qū)別在于session?C進(jìn)入時(shí)不會(huì)持續(xù)阻塞,session B異常后全部回滾
mysql連接超時(shí)怎么處理
MYSQL_OPT_READ_TIMEOUT 是 MySQL c api 客戶端中用來設(shè)置讀取超時(shí)時(shí)間的參數(shù)。在 MySQL 的官方文檔中,該參數(shù)的描述是這樣的:
MYSQL_OPT_READ_TIMEOUT (argument type: unsigned int *)The timeout in seconds for each attempt to read from the server. There are retries if necessary, so the total effective timeout value is three times the option value. You can set the value so that a lost connection can be detected earlier than the TCP/IPClose_Wait_Timeout value of 10 minutes.
也就是說在需要的時(shí)候,實(shí)際的超時(shí)時(shí)間會(huì)是設(shè)定值的 3 倍。但是實(shí)際測試后發(fā)現(xiàn)實(shí)際的超時(shí)時(shí)間和設(shè)置的超時(shí)時(shí)間一致。
而具體什么時(shí)候發(fā)生三倍超時(shí),在文檔中沒有找到。所以對 MySQL 5.7.20 的源碼進(jìn)行了一些分析。
使用 GDB 調(diào)試代碼找了實(shí)際與 mysql server 通信的代碼,如下:
請點(diǎn)擊輸入圖片描述
其中 vio_read() 函數(shù)中,使用 recv 和 poll 來讀取報(bào)文和做讀取超時(shí)。net_should_retry() 函數(shù)只有在發(fā)生 EINTR 時(shí)才會(huì)返回 true。從這段代碼來看是符合測試結(jié)果的,并沒有對讀取進(jìn)行三次重試。只有在讀取操作被系統(tǒng)中斷打斷時(shí)才會(huì)重試,但是這個(gè)重試并沒有次數(shù)限制。
從上面代碼的分析可以看出,代碼的邏輯和文檔的描述不符。于是在一頓搜索后,找到了一個(gè) MySQL 的 BUG(Bug #31163)。該 BUG 報(bào)告了在?MySQL?5.0 中,MySQL c api 讀取的實(shí)際超時(shí)時(shí)間是設(shè)置的三倍,與現(xiàn)有文檔描述相符。于是對 MySQL 5.0.96 的代碼又進(jìn)行分析。
同樣使用 GDB 找到了通信部分的代碼。這次找到了重試三次的代碼,如下:
請點(diǎn)擊輸入圖片描述
這個(gè)版本的 MySQL api 的讀寫超時(shí)是直接使用的 setsockopt 設(shè)置的。第一次循環(huán),在 A 點(diǎn)發(fā)生了第一次超時(shí)(雖然注釋寫的非阻塞,但是客戶端的連接始終是阻塞模式的)。然后在 B 點(diǎn)將該 socket 設(shè)置為阻塞模式,C 點(diǎn)這里重置 retry 次數(shù)。由于設(shè)置了 alarm 第二次以后的循環(huán)會(huì)直接進(jìn)入 D 點(diǎn)的這個(gè)分支,并且判斷循環(huán)次數(shù)。作為客戶端時(shí)net-retry_count 始終是 1,所以重試了兩次,共計(jì)進(jìn)行了 3 次 vioread 后從 E 點(diǎn)退出函數(shù)。
由上面的分析可知,MySQL 文檔對于該參數(shù)的描述已經(jīng)過時(shí),現(xiàn)在的 MYSQL_OPT_READ_TIMEOUT 并不會(huì)出現(xiàn)三倍超時(shí)的問題。而 Bug #31163 中的處理結(jié)果也是將文檔中該參數(shù)的描述更新為實(shí)際讀取超時(shí)時(shí)間是設(shè)定時(shí)間的三倍。也許是 MySQL 的維護(hù)者們在后續(xù)版本更新時(shí)忘記更新文檔吧。
名稱欄目:mysql鎖超時(shí)怎么處理,mysql死鎖處理方法
URL鏈接:http://www.ef60e0e.cn/article/hoegdi.html