mysql slave lock 跳过_slave开启MTS时执行mysqldump引发死锁案例
出現(xiàn)這種問(wèn)題除非手動(dòng)干預(yù),殺掉FTWRL的session,復(fù)制線程方可以繼續(xù)進(jìn)行。版本社區(qū)版5.7.26。
二、堵塞圖
如果分析上面的堵塞可以畫圖如下:
三、關(guān)于woker線程w1和w3的等待
這里我們需要重點(diǎn)關(guān)注參數(shù)?slave_preserve_commit_order,在我將要出版的《深入理解MySQL主從原理》一書中做了詳細(xì)描述,這里簡(jiǎn)單說(shuō)明如下:這個(gè)參數(shù)是為了保證從庫(kù) group commit 中的每個(gè)工作線程的事務(wù)提交順序和主庫(kù)事務(wù)執(zhí)行的順序一致。它在 order commit 的flush階段前就生效。工作線程的事務(wù)在等待獲取自己提交權(quán)限期間會(huì)堵塞在狀態(tài) ‘Waiting for preceding transaction to commit’ 下。
但是我們知道在order commit的flush之前就會(huì)獲取 MDL_key::COMMIT。因此這里w1和w3工作線程正在等待自己提交權(quán)限的到來(lái),但是遺憾的是w2的事務(wù)由于不能獲取 global read lock 而遲遲不能提交。同時(shí)它們堵塞了FTWRL。
四、關(guān)于FTWRL的等待
這個(gè)我也多次描述過(guò)了,FTWRL的過(guò)程大概如下:
第一步:?加MDL LOCK類型為GLOBAL,級(jí)別為S。如果出現(xiàn)等待狀態(tài)為 ‘Waiting for global read lock’。注意select語(yǔ)句不會(huì)上GLOBAL級(jí)別上鎖,但是DML/DDL/FOR UPDATE語(yǔ)句會(huì)上GLOBAL級(jí)別的IX鎖,IX鎖和S鎖不兼容會(huì)出現(xiàn)這種等待。下面是這個(gè)兼容矩陣:|?Type?of?active???|??Request?|???scoped?lock????|???type???|?IS(*)??IX???S??X?|?---------+------------------+?IS???????|??+??????+???+??+?|?IX???????|??+??????+???-??-?|?S????????|??+??????-???+??-?|?X????????|??+??????-???-??-?|
第二步:?推進(jìn)全局表緩存版本。源碼中就是一個(gè)全局變量 refresh_version++。?第三步:?釋放沒(méi)有使用的table 緩存。可自行參考函數(shù) close_cached_tables。?第四步:?判斷是否有正在占用的table緩存,如果有則等待,等待占用者釋放。等待狀態(tài)為 'Waiting for table flush'。這一步會(huì)去判斷table緩存的版本和全局表緩存版本是否匹配,如果不匹配則等待如下:for?(uint?idx=0?;?idx?has_old_version())?//如果版本?和?當(dāng)前?的?refresh_version?版本不一致???????{?????????found=?TRUE;?????????break;?//跳出第一層查找?是否有老版本?存在???????}?????}...if?(found)//如果找到老版本,需要等待???{?????/*???????The?method?below?temporarily?unlocks?LOCK_open?and?frees???????share's?memory.?????*/?????if?(share->wait_for_old_version(thd,?&abstime,???????????????????????????????????MDL_wait_for_subgraph::DEADLOCK_WEIGHT_DDL))?????{???????mysql_mutex_unlock(&LOCK_open);???????result=?TRUE;???????goto?err_with_reopen;?????}???}
而等待的結(jié)束就是占用的table緩存的占用者釋放,這個(gè)釋放操作存在于函數(shù) close_thread_table中,如下:if?(table->s->has_old_version()?||?table->needs_reopen()?||??????table_def_shutdown_in_progress)??{????tc->remove_table(table);//關(guān)閉?table?cache?instance????mysql_mutex_lock(&LOCK_open);????intern_close_table(table);//去掉?table?cache?define????mysql_mutex_unlock(&LOCK_open);??}
最終會(huì)調(diào)用函數(shù) MDL_wait::set_status 將 FTWRL 喚醒,也就是說(shuō)對(duì)于正在占用的table緩存釋放者不是FTWRL會(huì)話而是占用者自己。不管怎么樣最終整個(gè)table緩存將會(huì)被清空,如果經(jīng)過(guò)FTWRL后去查看 Open_table_definitions 和 Open_tables 將會(huì)發(fā)現(xiàn)重新計(jì)數(shù)了。下面是喚醒函數(shù)的代碼,也很明顯:bool?MDL_wait::set_status(enum_wait_status?status_arg)?open_table{??bool?was_occupied=?TRUE;??mysql_mutex_lock(&m_LOCK_wait_status);??if?(m_wait_status?==?EMPTY)??{????was_occupied=?FALSE;????m_wait_status=?status_arg;????mysql_cond_signal(&m_COND_wait_status);//喚醒??}??mysql_mutex_unlock(&m_LOCK_wait_status);//解鎖??return?was_occupied;}
第五步:?加MDL LOCK類型COMMIT 級(jí)別為S。如果出現(xiàn)等待狀態(tài)為 ‘Waiting for commit lock’。如果有大事務(wù)的提交很可能出現(xiàn)這種等待。
注意?這里的第五步,正是因?yàn)閣1和w3獲取了 MDL LOCK COMMIT,而又在等待w2的事務(wù)提交因此FTWRL也不得不等待。
五、關(guān)于woker線程w2的等待
這里可能的原因有2個(gè):多線程并行的情況下,線程執(zhí)行的順序本生就是不定的,很可能線程由于丟失CPU而落后其他線程的處理,因?yàn)镃PU調(diào)度的最小單位是線程。如果保證某個(gè)共享內(nèi)存操作的完整性需要用到mutex、原子變量等技術(shù)。
如果w2中的事務(wù)本生就包含了多個(gè)DML語(yǔ)句,那么獲取 GLOBAL READ LOCK 本身就是間歇性的,也就是每個(gè)語(yǔ)句結(jié)束都會(huì)釋放,然后下一個(gè)語(yǔ)句開始的時(shí)候再次open table來(lái)獲取。
我們來(lái)看看第二點(diǎn),只考慮row_format格式的binlog。
我們知道一個(gè)事務(wù)可以包含多個(gè)語(yǔ)句,每條語(yǔ)句都會(huì)包含一個(gè)map Event和多個(gè)DML Event,當(dāng)本Event是語(yǔ)句的最后一個(gè)Event的時(shí)候會(huì)使用STMT_END_F進(jìn)行標(biāo)記,也正是在這個(gè)時(shí)候會(huì)釋放 GLOBAL READ LOCK,源碼有如下:if?(get_flags(STMT_END_F))??{????if((error=?rows_event_stmt_cleanup(rli,?thd)))棧:#0??MDL_context::release_lock?(this=0x7fffa8000a08,?duration=MDL_STATEMENT,?ticket=0x7fffa800ea40)?at?/opt/percona-server-locks-detail-5.7.22/sql/mdl.cc:4350#1??0x0000000001464bf1?in?MDL_context::release_locks_stored_before?(this=0x7fffa8000a08,?duration=MDL_STATEMENT,?sentinel=0x0)?at?/opt/percona-server-locks-detail-5.7.22/sql/mdl.cc:4521#2??0x000000000146541b?in?MDL_context::release_statement_locks?(this=0x7fffa8000a08)?at?/opt/percona-server-locks-detail-5.7.22/sql/mdl.cc:4813#3??0x0000000001865c75?in?Relay_log_info::slave_close_thread_tables?(this=0x341e8b0,?thd=0x7fffa8000970)?at?/opt/percona-server-locks-detail-5.7.22/sql/rpl_rli.cc:2014#4??0x0000000001865873?in?Relay_log_info::cleanup_context?(this=0x341e8b0,?thd=0x7fffa8000970,?error=false)?at?/opt/percona-server-locks-detail-5.7.22/sql/rpl_rli.cc:1886#5??0x00000000017e8fc7?in?rows_event_stmt_cleanup?(rli=0x341e8b0,?thd=0x7fffa8000970)?at?/opt/percona-server-locks-detail-5.7.22/sql/log_event.cc:11782#6??0x00000000017e8c79?in?Rows_log_event::do_apply_event?(this=0x7fffa8017dc0,?rli=0x341e8b0)?at?/opt/percona-server-locks-detail-5.7.22/sql/log_event.cc:11660#7??0x00000000017cfdcd?in?Log_event::apply_event?(this=0x7fffa8017dc0,?rli=0x341e8b0)?at?/opt/percona-server-locks-detail-5.7.22/sql/log_event.cc:3570#8??0x00000000018476dc?in?apply_event_and_update_pos?(ptr_ev=0x7fffec14f880,?thd=0x7fffa8000970,?rli=0x341e8b0)?at?/opt/percona-server-locks-detail-5.7.22/sql/rpl_slave.cc:4766#9??0x0000000001848d9a?in?exec_relay_log_event?(thd=0x7fffa8000970,?rli=0x341e8b0)?at?/opt/percona-server-locks-detail-5.7.22/sql/rpl_slave.cc:5300#10?0x000000000184f9cc?in?handle_slave_sql?(arg=0x33769a0)?at?/opt/percona-server-locks-detail-5.7.22/sql/rpl_slave.cc:7543(gdb)?p?ticket->m_lock->key.mdl_namespace()$1?=?MDL_key::GLOBAL(gdb)?p?ticket->m_type$2?=?MDL_INTENTION_EXCLUSIVE(gdb)?p?ticket->m_duration$3?=?MDL_STATEMENT
如果下一條語(yǔ)句開始又會(huì)重新獲取GLOBAL READ LOCK,這就是我說(shuō)的間歇性獲取。
到這里死鎖條件已經(jīng)成熟,只要遇到這種情況就可能需要人為介入才能繼續(xù)了。
六、關(guān)于mysqldump
社區(qū)版在如下情況下需要增加FTWRL:設(shè)置了master-data
設(shè)置了singal-transaction和flush-logs
percona版在如下情況需要增加FTWRL:設(shè)置了singal-transaction和flush-logs
我們來(lái)大概看看社區(qū)版的代碼如下(代碼版本8.0.21),下面是從FTWRL倒UNLOCK的過(guò)程:if?((opt_lock_all_tables?||?opt_master_data?||?//如果設(shè)置了?master?data?設(shè)置flush?table?with?read?lock???????(opt_single_transaction?&&?flush_logs))?&&//如果設(shè)置了single?transaction和flush?logs?設(shè)置flush?table?with?read?lock??????do_flush_tables_read_lock(mysql))?//設(shè)置flush?table?with?read?lock????goto?err;??/*??/*????Flush?logs?before?starting?transaction?since????this?causes?implicit?commit?starting?mysql-5.5.??*/??if?(opt_lock_all_tables?||?opt_master_data?||???????(opt_single_transaction?&&?flush_logs)?||?opt_delete_master_logs)?{????if?(flush_logs?||?opt_delete_master_logs)?{//如果設(shè)置了?flush?logs?進(jìn)行日志刷新??????if?(mysql_refresh(mysql,?REFRESH_LOG))?{?//進(jìn)行日志刷新????????DB_error(mysql,?"when?doing?refresh");????????goto?err;??????}??????verbose_msg("--?main?:?logs?flushed?successfully!\n");????}????/*?Not?anymore!?That?would?not?be?sensible.?*/????flush_logs?=?false;??}??if?(opt_delete_master_logs)?{????if?(get_bin_log_name(mysql,?bin_log_name,?sizeof(bin_log_name)))?goto?err;??}??if?(opt_single_transaction?&&?start_transaction(mysql))?goto?err;?//開啟事務(wù)?RR??/*?Add?'STOP?SLAVE?to?beginning?of?dump?*/??if?(opt_slave_apply?&&?add_stop_slave())?goto?err;??/*?Process?opt_set_gtid_purged?and?add?SET?@@GLOBAL.GTID_PURGED?if?required.???*/??if?(process_set_gtid_purged(mysql))?goto?err;?//設(shè)置GTID,如果設(shè)置了gtid_purged?這個(gè)函數(shù)會(huì)跳過(guò)??if?(opt_master_data?&&?do_show_master_status(mysql))?goto?err;?//獲取主庫(kù)binlog位置??if?(opt_slave_data?&&?do_show_slave_status(mysql))?goto?err;?//slave_data?設(shè)置相關(guān)?從show?slave中獲取??if?(opt_single_transaction?&&??????do_unlock_tables(mysql))?/*?unlock?but?no?commit!?*/????goto?err;
percona版本中增加了判斷函數(shù) check_consistent_binlog_pos,如下(不過(guò)多描述):if?(opt_single_transaction?&&?opt_master_data)??{????/*???????See?if?we?can?avoid?FLUSH?TABLES?WITH?READ?LOCK?with?Binlog_snapshot_*???????variables.????*/????consistent_binlog_pos=?check_consistent_binlog_pos(NULL,?NULL);??}??if?((opt_lock_all_tables?||?(opt_master_data?&&?!consistent_binlog_pos)?||//consistent_binlog_pos?0?需要?1?不需要???????(opt_single_transaction?&&?flush_logs)))??{????if?(do_flush_tables_read_lock(mysql))??????goto?err;??}
七、如何解決
總結(jié)如下:master-data 一般備份都會(huì)增加,因此只能在低峰期進(jìn)行備份,盡量減少影響。
考慮關(guān)閉參數(shù) slave_preserve_commit_order。但是FTWRL的堵塞還是存在,只是不會(huì)產(chǎn)生死鎖。
如果壓力不大可以考慮關(guān)閉MTS。但是FTWRL的堵塞還是存在,只是不會(huì)產(chǎn)生死鎖。
全文完。
Enjoy MySQL :)
掃碼添加作者微信
葉老師的「MySQL核心優(yōu)化」大課已升級(jí)到MySQL 8.0,掃碼開啟MySQL 8.0修行之旅吧
總結(jié)
以上是生活随笔為你收集整理的mysql slave lock 跳过_slave开启MTS时执行mysqldump引发死锁案例的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 最新招聘公司网站 以及学校的宣讲会
- 下一篇: mysql的数据类型可分为哪两种_mys