本文對(duì) 5.6 主備場(chǎng)景下,在備庫做物理備份遇到死鎖的case進(jìn)行分析,希望對(duì)大家有所幫助。
創(chuàng)新互聯(lián)建站從2013年開始,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目網(wǎng)站制作、網(wǎng)站建設(shè)網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢(mèng)想脫穎而出為使命,1280元葫蘆島做網(wǎng)站,已為上家服務(wù),為葫蘆島各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:028-86922220
這里用的的物理備份工具是 Percona-XtraBackup(PXB),有的同學(xué)可能不清楚其備份流程,所以這里先簡單說下,PXB的備份步驟是這樣的:
如果 MyISAM 表很多話,全局讀鎖的持有時(shí)間會(huì)比較長,所以一般都在備庫做備份。
另外 FLUSH TABLE WITH READ LOCK 這條命令會(huì)獲取2個(gè)MDL鎖,全局讀鎖(MDL_key::GLOBAL)和全局COMMIT(MDL_key::COMMIT)鎖,MDL鎖詳情可以參考之前的月報(bào)MDL 實(shí)現(xiàn)分析。
我們先看一下死鎖時(shí)的現(xiàn)場(chǎng)是怎樣的:
MySQL> show processlist; +----+-------------+-----------------+------+---------+------+----------------------------------------+-------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+-------------+-----------------+------+---------+------+----------------------------------------+-------------------+ | 1 | root | 127.0.0.1:53309 | NULL | Query | 278 | init | show slave status | | 2 | system user | | NULL | Connect | 381 | Queueing master event to the relay log | NULL | | 3 | system user | | NULL | Connect | 311 | Waiting for commit lock | NULL | | 4 | root | 127.0.0.1:53312 | NULL | Query | 0 | init | show processlist | +----+-------------+-----------------+------+---------+------+----------------------------------------+-------------------+
可以看到 show slave status 被堵了很久,另外 SQL 線程在 Waiting for commit lock,說明在等待 COMMIT 鎖。
這時(shí)候如果我們?cè)龠B接進(jìn)去執(zhí)行 show slave status 也會(huì)被堵,并且即使 Ctrl-C kill 掉線程,線程依然還在。
mysql> show processlist; +----+-------------+-----------------+------+---------+------+----------------------------------------+-------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+-------------+-----------------+------+---------+------+----------------------------------------+-------------------+ | 1 | root | 127.0.0.1:53309 | NULL | Query | 753 | init | show slave status | | 2 | system user | | NULL | Connect | 856 | Queueing master event to the relay log | NULL | | 3 | system user | | NULL | Connect | 786 | Waiting for commit lock | NULL | | 4 | root | 127.0.0.1:53312 | NULL | Killed | 188 | init | show slave status | | 5 | root | 127.0.0.1:53314 | NULL | Query | 0 | init | show processlist | | 8 | root | 127.0.0.1:53318 | NULL | Killed | 125 | init | show slave status | | 11 | root | 127.0.0.1:53321 | NULL | Killed | 123 | init | show slave status | | 14 | root | 127.0.0.1:53324 | NULL | Query | 120 | init | show slave status | +----+-------------+-----------------+------+---------+------+----------------------------------------+-------------------+
pstack 看下相關(guān)線程的 backtrace,show slave status 線程的 backtrace 如下,非常明顯是在等mutex,對(duì)應(yīng)代碼為 mysql_mutex_lock(&mi->rli->data_lock):
#0 __lll_lock_wait #1 _L_lock_974 #2 __GI___pthread_mutex_lock #3 inline_mysql_mutex_lock #4 show_slave_status #5 mysql_execute_command #6 mysql_parse #7 dispatch_command #8 do_command #9 do_handle_one_connection #10 handle_one_connection ...
SQL 線程的 backtrace 如下,在等 COMMIT 鎖:
#0 pthread_cond_timedwait #1 inline_mysql_cond_timedwait #2 MDL_wait::timed_wait #3 MDL_context::acquire_lock #4 ha_commit_trans #5 trans_commit #6 Xid_log_event::do_commit #7 Xid_log_event::do_apply_event #8 Log_event::apply_event #9 apply_event_and_update_pos #10 exec_relay_log_event #11 handle_slave_sql ...
如果我們gdb進(jìn)去,去調(diào)試SQL線程,在 MDL_context::acquire_lock中:
(gdb) p (MDL_key::enum_mdl_namespace)lock->key->m_ptr[0] $24 = MDL_key::COMMIT (gdb) p ((THD*)lock->m_granted.m_list.m_first->m_ctx->m_owner)->thread_id $25 = 1
可以看到 COMMIT 鎖被線程 1 持有。
SQL線程在 Xid_log_event::do_commit 之前會(huì)持有 rli_ptr->data_lock。
所以現(xiàn)在就清楚了,是線程1(備份線程)和線程3(SQL線程)死鎖了,還原下死鎖過程:
就這樣2個(gè)線程互相持有等待,形成死鎖。
我們知道,MDL 是有死鎖檢測(cè)的,為什么這里沒有檢測(cè)到呢?因?yàn)閞li->data_lock是一個(gè)mutex,不屬于MDL系統(tǒng)的,在這個(gè)死鎖場(chǎng)景中,MDL鎖系統(tǒng)只能檢測(cè)到對(duì) COMMIT 鎖的請(qǐng)求,是不存在死鎖的。
之后的 show slave status 都被堵,是因?yàn)樵趫?zhí)行 show slave status 前,會(huì)請(qǐng)求一個(gè)mutex:
mysql_mutex_lock(&LOCK_active_mi); res= show_slave_status(thd, active_mi); mysql_mutex_unlock(&LOCK_active_mi);
之前死鎖的 show slave status 沒有退出,后面的 show slave status 自然堵在這個(gè) mutex 上,并且因?yàn)闊o法檢測(cè) thd->killed,所以一直無法退出。
死鎖的原因是SQL線程在提交的時(shí)候,持有 rli->data_lock 鎖,其實(shí)這個(gè)是不需要的,MySQL 官方在這個(gè) patch 中修復(fù)。
在上面的bug修復(fù)后,又出現(xiàn)了死鎖,但死鎖的情況卻不一樣,show processlist 結(jié)果如下:
mysql> show processlist; +----+-------------+-----------------+------+---------+------+----------------------------------+-------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+-------------+-----------------+------+---------+------+----------------------------------+-------------------+ | 2 | system user | | NULL | Connect | 436 | Waiting for master to send event | NULL | | 3 | system user | | NULL | Connect | 157 | Waiting for commit lock | NULL | | 6 | root | 127.0.0.1:42787 | NULL | Query | 86 | init | show slave status | | 7 | root | 127.0.0.1:42788 | NULL | Query | 96 | Killing slave | stop slave | | 8 | root | 127.0.0.1:42789 | NULL | Query | 0 | init | show processlist | +----+-------------+-----------------+------+---------+------+----------------------------------+-------------------+
依然是 SQL 線程在等待commit鎖,然后 show slave status 被堵住沒有返回,不同的是多了一個(gè) stop slave; 我們來看下 stop slave 的backtrace:
#0 pthread_cond_timedwait #1 inline_mysql_cond_timedwait #2 terminate_slave_thread #3 terminate_slave_threads #4 stop_slave #5 mysql_execute_command #6 mysql_parse #7 dispatch_command #8 do_command #9 do_handle_one_connection #10 handle_one_connection ...
對(duì)應(yīng)代碼,可以發(fā)現(xiàn) stop slave 正在等待 SQL 線程退出,而SQL線程此時(shí)正在等待備份線程(id=6)持有的 COMMIT 鎖。整個(gè)死鎖過程是這樣的:
這次是備份線程、SQL 線程、用戶線程3個(gè)線程互相持有等待,形成死鎖。
這次并不是代碼bug,算是一個(gè)用法問題,因此我們?cè)谶\(yùn)維過程中,如果發(fā)現(xiàn) SQL 線程在 Waiting for commit lock,就不要 stop slave。
如果不可避免出現(xiàn)了死鎖,該怎么解決呢?
通過上面的分析可以看到,不管是在 case 1 還是 case 2,備份線程和用戶線程都不再接受響應(yīng)了,要解決死鎖的話,只能 kill 掉 SQL 線程了,那么直接 kill 是否有風(fēng)險(xiǎn)呢?
SQL 線程能執(zhí)行 Xid event,說明是在更新事務(wù)引擎表,kill 掉應(yīng)該沒問題(事務(wù)可以回滾,之后可以重做),但是5.6有這樣的一個(gè)bug,會(huì)導(dǎo)致SQL線程在等待 COMMIT 鎖的時(shí)候被kill,直接跳過事務(wù),這樣備庫會(huì)比主庫少一個(gè)事務(wù),因此 kill 后需要對(duì)比主備數(shù)據(jù),把少的事務(wù)補(bǔ)上。
如果你使用的 MySQL 版本已經(jīng)修掉這個(gè)bug,也就是在 5.6.21 版本及之后,那么 kill SQL 線程是安全的。
如果為了測(cè)試或研究代碼,要想復(fù)現(xiàn)死鎖該怎么辦呢?如果直接在備庫執(zhí)行一個(gè) FTWRL,很可能是復(fù)現(xiàn)不了的,因?yàn)镕TWRL是獲取2個(gè)鎖,全局讀鎖和全局 COMMIT 鎖,SQL 線程非常可能被全局讀鎖堵到(Waiting for global read lock),而不是被 COMMIT 鎖堵(Waiting for commit lock)。
一種方法是寫 testcase,用 dubug sync 功能設(shè)置同步點(diǎn),讓線程停在指定的地方,但這要求 mysqld 跑在deubg模式下,并且要求有一定的MySQL 源碼開發(fā)基礎(chǔ);
另一種方法是改代碼,延長do_commit的時(shí)間,比如 sleep 一段時(shí)間,這樣就給我們足夠的時(shí)間讓 FTWRL 在 SQL 線程請(qǐng)求 COMMIT 鎖前執(zhí)行完成,但是這需改代碼,然后重新編譯安裝;
如果我們不會(huì)用debug sync,又不想改代碼重新編譯安裝,就想在已有的環(huán)境測(cè),改怎么辦呢?SYSTEMTAP!
systemtap 起初只支持在內(nèi)核空間進(jìn)行探測(cè),0.6 版本之后可以在用戶空間進(jìn)行探測(cè),使用 systemtap 需要程序中包含 debug 信息(程序編譯時(shí)加上 -g 選項(xiàng))。
列出所有我們可以對(duì) mysqld 進(jìn)行探測(cè)的地方。
sudo stap -L 'process("/usr/sbin/mysqld").function("*")'
列出所有可以對(duì) Xid_log_event 類進(jìn)行探測(cè)的地方。
sudo stap -L 'process("/usr/sbin/mysqld").function("*Xid_log_event::*")'
如果我們想讓 Xid_log_event::do_commit 執(zhí)行有點(diǎn)延遲,可以這樣做:
sudo stap -v -g -d /usr/bin/mysqld --ldd -e 'probe process(16011).function("Xid_log_event::do_commit") { printf("got it\n") mdelay(3000) }'
16011 是正在跑的備庫進(jìn)程PID,執(zhí)行上面的 stap 命令后,每當(dāng)備庫執(zhí)行到 Xid_log_event::do_commit 時(shí),stap 就會(huì)打出個(gè) “got it”,然后 SQL 線程暫停3s,這就給了我們充足的時(shí)間去執(zhí)行 FTWRL,在SQL線程 commit 前拿到 COMMIT 鎖。
轉(zhuǎn)自:http://www.kancloud.cn/taobaomysql/monthly/117960
分享標(biāo)題:MySQL·答疑解惑·物理備份死鎖分析
文章轉(zhuǎn)載:http://jinyejixie.com/article12/ghhegc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供搜索引擎優(yōu)化、、網(wǎng)站設(shè)計(jì)、網(wǎng)站設(shè)計(jì)公司、品牌網(wǎng)站設(shè)計(jì)、營銷型網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)