mysql 5.6的gtid_mode_[MySQL 5.6] GTID实现、运维变化及存在的bug
本文的主要目的是記下跟gtid相關(guān)的backtrace,用于以后的問題排查。另外也會(huì)討論目前在MySQL5.6.11版本中存在的bug。
前言:什么是GTID
什么是GTID呢, 簡而言之,就是全局事務(wù)ID(global transaction identifier ),最初由google實(shí)現(xiàn),官方MySQL在5.6才加入該功能,本文的起因在于5.6引入一大堆的gtid相關(guān)變量,深感困惑。
去年年中的時(shí)候,也寫過一片簡短的博客,大致介紹了下gtid是什么,http://mysqllover.com/?p=87。本文也不打算太多文字的介紹,因?yàn)榫W(wǎng)絡(luò)上已經(jīng)有大量的類似文章。
GTID的格式類似于:
7a07cd08-ac1b-11e2-9fcf-0010184e9e08:1
這是在我的一臺(tái)服務(wù)器上生成的gtid記錄,它在binlog中表現(xiàn)的事件類型就是:
GTID_LOG_EVENT:用于表示隨后的事務(wù)的GTID
另外還有兩種類型的GTID事件:
ANONYMOUS_GTID_LOG_EVENT :匿名GTID事件類型(暫且不論)
PREVIOUS_GTIDS_LOG_EVENT: 用于表示當(dāng)前binlog文件之前已經(jīng)執(zhí)行過的GTID集合,記錄在Binlog文件頭,例如:
# at 120#130502 23:23:27 server id 119821? end_log_pos 231 CRC32 0x4f33bb48???? Previous-GTIDs# 10a27632-a909-11e2-8bc7-0010184e9e08:1,# 7a07cd08-ac1b-11e2-9fcf-0010184e9e08:1-1129
這個(gè)字符串,用“:”分開,前面表示這個(gè)服務(wù)器的server_uuid,這是一個(gè)128位的隨機(jī)字符串,在第一次啟動(dòng)時(shí)生成(函數(shù)generate_server_uuid),對(duì)應(yīng)的variables是只讀變量server_uuid。 它能以極高的概率保證全局唯一性,并存到文件DATA/auto.cnf中。因此要注意保護(hù)這個(gè)文件不要被刪除或修改,不然就麻煩了。
第二部分是一個(gè)自增的事務(wù)ID號(hào),事務(wù)id號(hào)+server_uuid來唯一標(biāo)示一個(gè)事務(wù)。
除了單獨(dú)的GTID外,還有一個(gè)GTID SET的概念。一個(gè)GTID SET的表示類似于:
7a07cd08-ac1b-11e2-9fcf-0010184e9e08:1-31
GTID_EXECUTED和GTID_PURGED是典型的GTID SET類型變量;在一個(gè)復(fù)制拓?fù)渲?#xff0c;GTID_EXECUTED 可能包含好幾組數(shù)據(jù),例如:
mysql> show global variables like ‘%gtid_executed%’\G
*************************** 1. row ***************************
Variable_name: gtid_executed
Value: 10a27632-a909-11e2-8bc7-0010184e9e08:1-4,
153c0406-a909-11e2-8bc7-0010184e9e08:1-3,
7a07cd08-ac1b-11e2-9fcf-0010184e9e08:1-31,
f914fb74-a908-11e2-8bc6-0010184e9e08:1
本文討論的內(nèi)容包括:
一.主庫上的gtid產(chǎn)生及記錄
二.備庫如何使用GTID復(fù)制
三.主備運(yùn)維的變化
四.MySQL5.6.11存在的bug
一、主庫上的Gtid
a.相關(guān)變量
主庫上每個(gè)事務(wù)的Gtid包括變化的部分和不變的部分。在討論之前,要弄清楚GTID維護(hù)的四個(gè)變量:
GTID_PURGED:已經(jīng)被刪除的binlog的事務(wù),它是GTID_EXECUTED的子集,從MySQL5.6.9開始,該變量無法被設(shè)置。
GTID_OWNED:??表示正在執(zhí)行的事務(wù)的gtid以及對(duì)應(yīng)的線程ID。
例如如下:
mysql> show global variables like ‘%gtid_owned%’\G
*************************** 1. row ***************************
Variable_name: gtid_owned
Value: 7a07cd08-ac1b-11e2-9fcf-0010184e9e08:11560057#67:11560038#89:11560059#7:11560034#32:11560053#56:11560052#112:11560055#128:11560054#65:11559997#96:11560056#90:11560051#85:11560058#39:11560061#12:11560060#125:11560035#62:11560062#5
1 row in set (0.01 sec)
GTID_EXECUTED表示已經(jīng)在該實(shí)例上執(zhí)行過的事務(wù); 執(zhí)行RESET MASTER 會(huì)將該變量置空; 我們還可以通過設(shè)置GTID_NEXT執(zhí)行一個(gè)空事務(wù),來影響GTID_EXECUTED
GTID_NEXT是SESSION級(jí)別變量,表示下一個(gè)將被使用的GTID
在內(nèi)存中也維護(hù)了與GTID_PURGED, GTID_OWNED, GTID_EXECUTED相對(duì)應(yīng)的全局對(duì)象gtid_state。
gtid_state中維護(hù)了三個(gè)集合,其中l(wèi)ogged_gtids對(duì)應(yīng)GTID_EXECUTED,?lost_gtids對(duì)應(yīng)GTID_PURGED,owned_gtids對(duì)應(yīng)GTID_OWNED
b.如何分配和使用GTID
在主庫執(zhí)行一個(gè)事務(wù)的過程中,關(guān)于Gtid主要涉及到以下幾個(gè)部分:
事務(wù)開始,執(zhí)行第一條SQL時(shí),在寫入第一個(gè)“BEGIN” 的QUERY EVENT 之前, 為binlog cache 的Group_cache中分配一個(gè)group(Group_cache::add_logged_group),并寫入一個(gè)Gtid_log_event,此時(shí)并未為其分配事務(wù)id,backtrace 如下:
handler::ha_write_row->binlog_log_row->write_locked_table_maps->THD::binlog_write_table_map->binlog_start_trans_and_stmt->binlog_cache_data::write_event->Group_cache::add_logged_group
暫時(shí)還不清楚什么時(shí)候一個(gè)事務(wù)里會(huì)有多個(gè)gtid的group_cache.
在binlog group commit的flush階段:
第一步,調(diào)用Group_cache::generate_automatic_gno來為當(dāng)前線程生成一個(gè)gtid,分配給thd->owned_gtid,并加入到owned_gtids中,backtrace如下:
MYSQL_BIN_LOG::process_flush_stage_queue->MYSQL_BIN_LOG::flush_thread_caches->binlog_cache_mngr::flush->binlog_cache_data::flush->gtid_before_write_cache->Group_cache::generate_automatic_gno->Gtid_state::acquire_ownership->Owned_gtids::add_gtid_owner
也就是說,直到事務(wù)完成,準(zhǔn)備把binlog刷到binlog cache時(shí),才會(huì)去為其分配gtid.
當(dāng)gtid_next的類型為AUTOMATIC時(shí),調(diào)用generate_automatic_gno生成事務(wù)id(gno),分配流程大概如下:
1.gtid_state->lock_sidno(automatic_gtid.sidno) , 為當(dāng)前sidno加鎖,分配過程互斥
2.gtid_state->get_automatic_gno(automatic_gtid.sidno); 獲取事務(wù)ID
|–>初始化候選(candidate)gno為1
|–>從logged_gtids[$sidno]中掃描,獲取每個(gè)gno區(qū)間(iv):
|–>當(dāng)candidate < iv->start(或者M(jìn)AX_GNO,如果iv為NULL)時(shí),判斷candidate是否有被占用,如果沒有的話,則使用該candidate,從函數(shù)返回,否則candidate++,繼續(xù)本步驟
|–>將candidate設(shè)置為iv->end,iv指向下一個(gè)區(qū)間,繼續(xù)第二步
從該過程可以看出,這里兼顧了區(qū)間存在碎片的場景,有可能分配的gno并不是全局最大的gno. 不過在主庫不手動(dòng)設(shè)置gtid_next的情況下,我們可以認(rèn)為主庫上的gno總是遞增的。
3.gtid_state->acquire_ownership(thd, automatic_gtid);
|–>加入到owned_gtids集合中(owned_gtids.add_gtid_owner),并賦值給thd->owned_gtid= gtid
4.gtid_state->unlock_sidno(automatic_gtid.sidno); ?解鎖
第二步, 調(diào)用Gtid_state::update_on_flush將當(dāng)前事務(wù)的grid加入到logged_gtids中,backtrace如下:
MYSQL_BIN_LOG::process_flush_stage_queue->MYSQL_BIN_LOG::flush_thread_caches->binlog_cache_mngr::flush->binlog_cache_data::flush->MYSQL_BIN_LOG::write_cache->Gtid_state::update_on_flush
在bin log group commit的commit階段
調(diào)用Gtid_state::update_owned_gtids_impl 從owned_gtids中將當(dāng)前事務(wù)的gtid移除,backtrace 如下:
MYSQL_BIN_LOG::ordered_commit->MYSQL_BIN_LOG::finish_commit->Gtid_state::update_owned_gtids_impl
上述步驟涉及到的是對(duì)logged_gtids和owned_gtids的修改。而lost_gtids除了啟動(dòng)時(shí)維護(hù)外,就是在執(zhí)行Purge操作時(shí)維護(hù)。
例如,當(dāng)我們執(zhí)行purge binary logs to ‘mysql-bin.000205′ 時(shí), mysql-bin.index先被更新掉,然后再根據(jù)index文件找到第一個(gè)binlog文件的PREVIOUS_GTIDS_LOG_EVENT事件,更新lost_gtids集合,backtrace如下:
purge_master_logs->MYSQL_BIN_LOG::purge_logs->MYSQL_BIN_LOG::init_gtid_sets->read_gtids_from_binlog->Previous_gtids_log_event::add_to_set->Gtid_set::add_gtid_encoding->Gtid_set::add_gno_interval
關(guān)于binlog group commit,參見之前寫的博客:
c.如何持久化GTID
當(dāng)重啟MySQL后,我們看到GTID_EXECUTED和GTID_PURGED和重啟前是一致的。
持久化GTID,是通過全局對(duì)象gtid_state來管理的。gtid_state在系統(tǒng)啟動(dòng)時(shí)調(diào)用函數(shù)gtid_server_init分配內(nèi)存;如果打開了binlog,則會(huì)做進(jìn)一步的初始化工作:
quoted code:
5419?????? if (mysql_bin_log.init_gtid_sets(
5420???????????? const_cast(gtid_state->get_logged_gtids()),
5421???????????? const_cast(gtid_state->get_lost_gtids()),
5422???????????? opt_master_verify_checksum,
5423???????????? true/*true=need lock*/))
5424???????? unireg_abort(1);
gtid_state 包含3個(gè)gtid集合:logged_gtids,?lost_gtids,?owned_gtids,前兩個(gè)都是gtid_set類型, owned_gtids類型為Owned_gtids
MYSQL_BIN_LOG::init_gtid_sets 主要用于初始化logged_gtids和lost_gtids,該函數(shù)的邏輯簡單描述下:
1.掃描mysql-index文件,搜集binlog文件名,并加入到filename_list中
2.從最后一個(gè)文件開始往前讀,依次調(diào)用函數(shù)read_gtids_from_binlog:
|–>打開binlog文件,如果讀取到PREVIOUS_GTIDS_LOG_EVENT事件
(1)無論如何,將其加入到logged_gtids(prev_gtids_ev->add_to_set(all_gtids))
(2)如果該文件是第一個(gè)binlog文件,將其加入到lost_gtids(prev_gtids_ev->add_to_set(prev_gtids))中.
|–>獲取GTID_LOG_EVENT事件
(1) 讀取該事件對(duì)應(yīng)的sidno,sidno= gtid_ev->get_sidno(false);
這是一個(gè)32位的整型,用sidno來代表一個(gè)server_uuid,從1開始計(jì)算,這主要處于節(jié)省內(nèi)存的考慮。維護(hù)在全局對(duì)象global_sid_map中。
當(dāng)sidno還沒加入到map時(shí),調(diào)用global_sid_map->add_sid(sid),sidno從1開始遞增。
(2) all_gtids->ensure_sidno(sidno)
all_gtids是gtid_set類型,可以理解為一個(gè)集合,ensure_sidno就是要確保這個(gè)集合至少可以容納sidno個(gè)元素
(3) all_gtids->_add_gtid(sidno, gtid_ev->get_gno()
將該事件中記錄的gtid加到all_gtids[sidno]中(最終調(diào)用Gtid_set::add_gno_interval,這里實(shí)際上是把(gno, gno+1)這樣一個(gè)區(qū)間加入到其中,這里
面涉及到區(qū)間合并,交集等操作 ? ?)
當(dāng)?shù)谝粋€(gè)文件中既沒有PREVIOUS_GTIDS_LOG_EVENT, 也沒有GTID_LOG_EVENT時(shí),就繼續(xù)讀上一個(gè)文件
如果只存在PREVIOUS_GTIDS_LOG_EVENT事件,函數(shù)read_gtids_from_binlog返回GOT_PREVIOUS_GTIDS
如果還存在GTID_LOG_EVENT事件,返回GOT_GTIDS
這里很顯然存在一個(gè)問題,即如果在重啟前,我們并沒有使用gtid_mode,并且產(chǎn)生了大量的binlog,在這次重啟后,我們就可能需要掃描大量的binlog文件。這是一個(gè)非常明顯的Bug, 后面再集中討論。
3.如果第二部掃描,沒有到達(dá)第一個(gè)文件,那么就從第一個(gè)文件開始掃描,和第2步流程類似,讀取到第一個(gè)PREVIOUS_GTIDS_LOG_EVENT事件,并加入到lost_gtids中。
簡單的講,如果我們一直打開的gtid_mode,那么只需要讀取第一個(gè)binlog文件和最后一個(gè)binlog文件,就可以確定logged_gtids和lost_gtids這兩個(gè)GTID SET了。
二、備庫上的GTID
a.如何保持主備GTID一致
由于在binlog中記錄了每個(gè)事務(wù)的GTID,因此備庫的復(fù)制線程可以通過設(shè)置線程級(jí)別GTID_NEXT來保證主庫和備庫的GTID一致。
默認(rèn)情況下,主庫上的thd->variables.gtid_next.type為AUTOMATIC_GROUP,而備庫為GTID_GROUP
備庫SQL線程gtid_next輸出:
(gdb) p thd->variables.gtid_next$2 = {type = GTID_GROUP,gtid = {sidno = 2,gno = 1127,static MAX_TEXT_LENGTH = 56},static MAX_TEXT_LENGTH = 56}
C/C++ 中的 gdb 也是一個(gè)類似的命令行 debugger,只是用來調(diào)試 C/C++ 而已,使用的模式跟Python的pdb/ipdb相似,具體可參考 用GDB調(diào)試程序。
這些變量在執(zhí)行Gtid_log_event時(shí)被賦值:Gtid_log_event::do_apply_event,大體流程為:
1.rpl_sidno sidno= get_sidno(true); ?獲取sidno
2.thd->variables.gtid_next.set(sidno, spec.gtid.gno); ?設(shè)置gtid_next
3.gtid_acquire_ownership_single(thd);
|–>檢查該gtid是否在logged_gtids集合中,如果在的話,則返回(gtid_pre_statement_checks會(huì)忽略該事務(wù))
|–>如果該gtid已經(jīng)被其他線程擁有,則等待(gtid_state->wait_for_gtid(thd, gtid_next)),否則將當(dāng)前線程設(shè)置為owner(gtid_state->acquire_ownership(thd, gtid_next))
在上面提到,有可能當(dāng)前事務(wù)的GTID已經(jīng)在logged_gtids中,因此在執(zhí)行Rows_log_event::do_apply_event或者mysql_execute_command函數(shù)中,都會(huì)去調(diào)用函數(shù)gtid_pre_statement_checks
該函數(shù)也會(huì)在每個(gè)SQL執(zhí)行前,檢查gtid是否合法,主要流程包括:
1.當(dāng)打開選項(xiàng)enforce_gtid_consistency時(shí),檢查DDL是否被允許執(zhí)行(thd->is_ddl_gtid_compatible()),若不允許,返回GTID_STATEMENT_CANCEL
2.檢查當(dāng)前SQL是否會(huì)產(chǎn)生隱式提交并且gtid_next被設(shè)置(gtid_next->type != AUTOMATIC_GROUP),如果是的話,則會(huì)拋出錯(cuò)誤ER_CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET 并返回GTID_STATEMENT_CANCEL,注意這里會(huì)導(dǎo)致bug#69045
3.對(duì)于BEGIN/COMMIT/ROLLBACK/(SET OPTION 或者 SELECT )且沒有使用存儲(chǔ)過程/ 這幾種類型的SQL,總是允許執(zhí)行,返回GTID_STATEMENT_EXECUTE
4.gtid_next->type為UNDEFINED_GROUP,拋出錯(cuò)誤ER_GTID_NEXT_TYPE_UNDEFINED_GROUP,返回GTID_STATEMENT_CANCEL
5.gtid_next->type == GTID_GROUP且thd->owned_gtid.sidno == 0時(shí), 返回GTID_STATEMENT_SKIP
其中第五步中處理了函數(shù)gtid_acquire_ownership_single的特殊情況
b.備庫如何發(fā)起DUMP請(qǐng)求
引入GTID,最大的好處當(dāng)然是我們可以隨心所欲的切換主備拓?fù)浣Y(jié)構(gòu)了。在一個(gè)正常運(yùn)行的復(fù)制結(jié)構(gòu)中,我們可以在備庫簡單的執(zhí)行如下SQL:
CHANGE MASTER TO MASTER_USER=’$USERNAME’, MASTER_HOST=’ ‘, MASTER_PORT=’ ‘, MASTER_AUTO_POSITION=1;
打開GTID后,我們就無需指定binlog文件或者位置,MySQL會(huì)自動(dòng)為我們做這些事情。這里的關(guān)鍵就是MASTER_AUTO_POSITION。IO線程連接主庫,可以大概分為以下幾步:
1.IO線程在和主庫建立TCP鏈接后,會(huì)去獲取主庫的uuid(get_master_uuid),然后在主庫上設(shè)置一個(gè)用戶變量@slave_uuid(io_thread_init_commands)
2.之后,在主庫上注冊(cè)SLAVE(register_slave_on_master)
在主庫上調(diào)用register_slave來注冊(cè)備庫,將備庫的host,user,password,port,server_id等信息記錄到slave_list哈希中。
3.調(diào)用request_dump,開始向主庫請(qǐng)求數(shù)據(jù),這里分兩種情況:
MASTER_AUTO_POSITION=0時(shí),向主庫發(fā)送命令的類型為COM_BINLOG_DUMP,這是傳統(tǒng)的請(qǐng)求BINLOG的模式
MASTER_AUTO_POSITION=1時(shí),命令類型為COM_BINLOG_DUMP_GTID,這是新的方式。
這里我們只討論第二種。第二種情況下,會(huì)先去讀取備庫已經(jīng)執(zhí)行的gtid集合
quoted code in rpl_slave.cc :
2974?? if (command == COM_BINLOG_DUMP_GTID)
2975?? {
2976???? // get set of GTIDs
2977???? Sid_map sid_map(NULL/*no lock needed*/);
2978???? Gtid_set gtid_executed(&sid_map);
2979???? global_sid_lock->wrlock();
2980???? gtid_state->dbug_print();
2981???? if (gtid_executed.add_gtid_set(mi->rli->get_gtid_set()) != RETURN_STATUS_OK ||
2982???????? gtid_executed.add_gtid_set(gtid_state->get_logged_gtids()) !=
2983???????? RETURN_STATUS_OK)
構(gòu)建完成發(fā)送包后,發(fā)送給主庫。
在主庫上接受到命令后,調(diào)用入口函數(shù)com_binlog_dump_gtid,流程如下:
1.slave_gtid_executed.add_gtid_encoding(packet_position, data_size) ;讀取備庫傳來的GTID SET
2.讀取備庫的uuid(get_slave_uuid),被根據(jù)uuid來kill僵尸線程(kill_zombie_dump_threads)
這也是之前SLAVE IO線程執(zhí)行SET @SLAVE_UUID的用處。
3.進(jìn)入mysql_binlog_send函數(shù):
|–>調(diào)用MYSQL_BIN_LOG::find_first_log_not_in_gtid_set,從最后一個(gè)Binlog開始掃描,獲取文件頭部的PREVIOUS_GTIDS_LOG_EVENT,如果它是slave_gtid_executed的子集,保存當(dāng)前binlog文件名,否則繼續(xù)向前掃描。
這一步的目的就是為了找出備庫執(zhí)行到的最后一個(gè)Binlog文件。
|–>從這個(gè)文件頭部開始掃描,遇到GTID_EVENT時(shí),會(huì)去判斷該GTID是否包含在slave_gtid_executed中:
Gtid_log_event gtid_ev(packet->ptr() + ev_offset,
packet->length() – checksum_size,
p_fdle);
skip_group= slave_gtid_executed->contains_gtid(gtid_ev.get_sidno(sid_map),
gtid_ev.get_gno());
主庫通過GTID決定是否可以忽略事務(wù),從而決定執(zhí)行開始的位置
注意,在使用MASTER_LOG_POSITION后,就不要指定binlog的位置,否則會(huì)報(bào)錯(cuò)。
三、運(yùn)維操作
a.如何忽略復(fù)制錯(cuò)誤
當(dāng)備庫復(fù)制出錯(cuò)時(shí),傳統(tǒng)的跳過錯(cuò)誤的方法是設(shè)置sql_slave_skip_counter,然后再START SLAVE。
但如果打開了GTID,就會(huì)設(shè)置失敗:
mysql> set global sql_slave_skip_counter = 1;
ERROR 1858 (HY000): sql_slave_skip_counter can not be set when the server is running with @@GLOBAL.GTID_MODE = ON. Instead, for each transaction that you want to skip, generate an empty transaction with the same GTID as the transaction
提示的錯(cuò)誤信息告訴我們,可以通過生成一個(gè)空事務(wù)來跳過錯(cuò)誤的事務(wù)。
我們手動(dòng)產(chǎn)生一個(gè)備庫復(fù)制錯(cuò)誤:
Last_SQL_Error: Error ‘Unknown table ‘test.t1” on query. Default database: ‘test’. Query: ‘DROP TABLE `t1` /* generated by server */’
查看binlog中,該DDL對(duì)應(yīng)的GTID為7a07cd08-ac1b-11e2-9fcf-0010184e9e08:1131
在備庫上執(zhí)行:
mysql> STOP SLAVE;
Query OK, 0 rows affected (0.00 sec)
mysql> SET SESSION GTID_NEXT = ’7a07cd08-ac1b-11e2-9fcf-0010184e9e08:1131′;
Query OK, 0 rows affected (0.00 sec)
mysql> BEGIN; COMMIT;
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
mysql> SET SESSION GTID_NEXT = AUTOMATIC;
Query OK, 0 rows affected (0.00 sec)
mysql> START SLAVE;
再查看show slave status,就會(huì)發(fā)現(xiàn)錯(cuò)誤事務(wù)已經(jīng)被跳過了。這種方法的原理很簡單,空事務(wù)產(chǎn)生的GTID加入到GTID_EXECUTED中,這相當(dāng)于告訴備庫,這個(gè)GTID對(duì)應(yīng)的事務(wù)已經(jīng)執(zhí)行了。
b.重指主庫
使用change master to …. , MASTER_AUTO_POSITION=1;
注意在整個(gè)復(fù)制拓?fù)渲?#xff0c;都需要打開gtid_mode
c.新的until條件
5.6提供了新的util condition,可以根據(jù)GTID來決定備庫復(fù)制執(zhí)行到的位置
SQL_BEFORE_GTIDS:在指定的GTID之前停止復(fù)制
SQL_AFTER_GTIDS :在指定的GTID之后停止復(fù)制
判斷函數(shù)為Relay_log_info::is_until_satisfied
d.適當(dāng)減小binlog文件的大小
如果開啟GTID,理論上最好調(diào)小每個(gè)binlog文件的最大值,以縮小掃描文件的時(shí)間。
四、存在的bug
bug#69097, 即使關(guān)閉了gtid_mode,也會(huì)在啟動(dòng)時(shí)去掃描binlog文件。
當(dāng)在重啟前沒有使用gtid_mode,重啟后可能會(huì)去掃描所有的binlog文件,如果Binlog文件很多的話,這顯然是不可接受的。
bug#69096,無法通過GTID_NEXT_LIST來跳過復(fù)制錯(cuò)誤,因?yàn)槟J(rèn)編譯下,GTID_NEXT_LIST未被編譯進(jìn)去。
TODO:GTID_NEXT_LIST的邏輯上面均未提到,有空再看。
bug#69095,將備庫的復(fù)制模式設(shè)置為STATEMENT/MIXED。 主庫設(shè)置為ROW模式,執(zhí)行DML 會(huì)導(dǎo)致備庫復(fù)制中斷
Last_SQL_Error: Error executing row event: ‘Cannot execute statement: impossible to write to binary log since statement is in row format and BINLOG_FORMAT = STATEMENT.’
判斷報(bào)錯(cuò)的backtrace:
handle_slave_worker->slave_worker_exec_job->Rows_log_event::do_apply_event->open_and_lock_tables->open_and_lock_tables->lock_tables->THD::decide_logging_format
解決辦法:將備庫的復(fù)制模式設(shè)置為’ROW’ ,保持主備一致
該bug和GTID無關(guān)
bug#69045, 當(dāng)主庫執(zhí)行類似 FLUSH PRIVILEGES這樣的動(dòng)作時(shí),如果主庫和備庫都開啟了gtid_mode,會(huì)導(dǎo)致復(fù)制中斷
Last_SQL_Error: Error ‘Cannot execute statements with implicit commit inside a transaction when @@SESSION.GTID_NEXT != AUTOMATIC or @@SESSION.GTID_NEXT_LIST != NULL.’ on query. Default database: ”. Query: ‘flush privileges’
也是一個(gè)很低級(jí)的bug,在MySQL5.6.11版本中,如果有可能導(dǎo)致隱式提交的事務(wù), 則gtid_next必須等于AUTOMATIC,對(duì)備庫復(fù)制線程而言,很容易就中斷了,判斷邏輯在函數(shù)gtid_pre_statement_checks中
參考文檔
1.阿里長源的三篇博客(一,?二, 三)
2.MySQL5.6.11源代碼
http://mysql.taobao.org/monthly/2020/05/09/
GTID的生成和使用由以下幾步組成:
主服務(wù)器更新數(shù)據(jù)時(shí),會(huì)在事務(wù)前產(chǎn)生GTID,一同記錄到binlog日志中。
binlog傳送到從服務(wù)器后,被寫入到本地的relay log中。從服務(wù)器讀取GTID,并將其設(shè)定為自己的GTID(GTID_NEXT系統(tǒng))。
sql線程從relay log中獲取GTID,然后對(duì)比從服務(wù)器端的binlog是否有記錄。
如果有記錄,說明該GTID的事務(wù)已經(jīng)執(zhí)行,從服務(wù)器會(huì)忽略。
如果沒有記錄,從服務(wù)器就會(huì)從relay log中執(zhí)行該GTID的事務(wù),并記錄到binlog。
GTID_OWNED:
表示正在執(zhí)行的事務(wù)的GTID以及其對(duì)應(yīng)的線程ID。
Scope : Global, Session
Dynamic : No
Type : String
如果GDIT_OWNED是全局變量,它包含所有當(dāng)前服務(wù)器上正在使用的GTIDs和使用它們的線程IDs。這個(gè)變量主要用于并行復(fù)制,從而可以查看一個(gè)事務(wù)是否已經(jīng)被另一個(gè)線程處理。這個(gè)線程會(huì)擁有所處理事務(wù)的ownership。@@global.grid_owned會(huì)顯示出GTID和它的owner。當(dāng)事務(wù)處理完成,線程會(huì)釋放ownership. 如果GDIT_OWNED是session變量,它包含一個(gè)seesion正在使用的GTID。這個(gè)變量對(duì)測試和debug會(huì)很有幫助
gtid在各個(gè)mysql節(jié)點(diǎn)的binlog里面都是全局唯一
f
總結(jié)
以上是生活随笔為你收集整理的mysql 5.6的gtid_mode_[MySQL 5.6] GTID实现、运维变化及存在的bug的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux下的gpio转串口驱动,X-0
- 下一篇: 如何卸载mysql server 200