日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
MySQLBinlog組提交實(shí)現(xiàn)

本文代碼分析基于 MySQL 8.0.29

創(chuàng)新互聯(lián)2013年至今,先為青羊等服務(wù)建站,青羊等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為青羊企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。

1.背景

MySQL 提交流程有兩個(gè)問題需要解決:

1.1. 提交寫兩份日志的性能問題

為了保證事務(wù)的持久性和原子性,事務(wù)提交完成前,其日志(WAL)必須持久化。對于 MySQL 來說,需要保證事務(wù)提交前,redo log 落盤。雖然日志順序?qū)懙男阅埽呀?jīng)高于數(shù)據(jù)文件隨機(jī)寫的性能,但是如果每次事務(wù)提交,都需將 redo log 刷盤,效率較低。同時(shí) MySQL 還要寫 binlog,相當(dāng)于每次事務(wù)提交需要兩次 IO,很容易成為性能瓶頸。

為了解決上述性能問題,經(jīng)過 MySQL 5.6/5.7/8.0 的不斷優(yōu)化,引入組提交技術(shù)和流水線技術(shù)。

1.2. redo log/binlog 的原子性和一致性

原子性比較好解決,MySQL 利用一個(gè)內(nèi)部 2PC 機(jī)制實(shí)現(xiàn) redo log 和 binlog 的原子提交,其中2PC 的協(xié)調(diào)者由 binlog 承擔(dān)。

// mysqld.cc, init_server_components

if (total_ha_2pc > 1 || (1 == total_ha_2pc && opt_bin_log)) {

if (opt_bin_log)

// tc means transaction coordinator

tc_log = &mysql_bin_log;

else

tc_log = &tc_log_mmap;

}

內(nèi)部兩階段提交的流程簡單描述為:

Prepare 階段 :(1)InnoDB 將回滾段上的事務(wù)狀態(tài)設(shè)置為 PREPARED;(2)將 redolog 寫文件并刷盤;

Commit 階段 :(1)Binlog 寫入文件;(2)binlog 刷盤;(3)InnoDB commit;

兩階段提交的 commit point 是 binlog 刷盤成功(因?yàn)榇藭r(shí)兩個(gè)日志都持久化成功了)。Recovery 流程會比較 binlog xid 和 redo xid,判斷事務(wù)是否達(dá)到 commit point,以此來決定提交還是回滾:

  • 如果 binlog 還未刷盤,即使 redo log 已經(jīng)刷盤了也要回滾。
  • 如果 binlog 已經(jīng)刷盤,即使 InnoDB commit 還未完成,也要重新寫入 commit 標(biāo)志,完成提交。

解決完原子性的問題,還有一致性問題。事務(wù)binlog 提交的順序應(yīng)該和 redo log 保持一致,否則可能物理備份(不拷貝 binlog)丟數(shù)據(jù)的問題(可以參考該文章給出的例子 https:// blog./u_151276 00/3998295 )。但是 Xtrabackup 在這次提交后 https:// jira.percona.com/browse /PXB-1770 ,通過備份 binlog 避免了這種問題。

MySQL5.6以前,為保證 binlog 和 redo log 提交順序一致,MySQL 使用了prepare_commit_mutex 鎖,事務(wù)在兩階段提交流程中持有它,這樣確實(shí)可以保證兩份日志順序一致,但它也會導(dǎo)致事務(wù)串行執(zhí)行,效率很低。后來組提交和流水線提交的引入,不僅減少了 IO 次數(shù),還提高了事務(wù)執(zhí)行的并發(fā)度,減小了加鎖的粒度。

2. 提交流水線

為解決上節(jié)提到的兩個(gè)問題,經(jīng)過 5.6/5.7/8.0 的逐步優(yōu)化,兩階段提交的邏輯優(yōu)化為:

  • Prepare 階段基本不變,只是寫 redolog 時(shí)并不刷盤。
  • Commit 階段按步驟做流水線批處理,將鎖粒度進(jìn)一步拆細(xì)。
  • Commit 階段又拆為三個(gè)主要步驟:
  • flush stage:按事務(wù)進(jìn)入的順序?qū)?binlog 從 cache 寫入文件(不刷盤),redo log 刷盤(多個(gè)事務(wù) redo 合并刷盤)。
  • sync stage:對 binlog 文件做 fsync 操作(多個(gè)事務(wù)的 binlog 合并刷盤)。
  • commit stage:各個(gè)線程按順序做 InnoDB commit 操作。

每個(gè) stage 一個(gè)隊(duì)列,第一個(gè)進(jìn)入該隊(duì)列的線程成為 leader,后續(xù)進(jìn)入的線程會阻塞直至完成提交。leader 線程會領(lǐng)導(dǎo)隊(duì)列中的所有線程執(zhí)行該 stage 的任務(wù),并帶領(lǐng)所有 follower 進(jìn)入到下一個(gè) stage 去執(zhí)行,當(dāng)遇到下一個(gè) stage 為非空隊(duì)列時(shí),leader 會變成 follower 注冊到此隊(duì)列中。

而 redo log 刷盤從 Prepare 階段移動到 flush stage,這樣 leader 也可以將多個(gè)事務(wù)的 redo log 合并刷盤。同樣 sync stage 的 leader 可以將多個(gè)事務(wù)的 binlog 合并刷盤。

每一個(gè) stage 都是加鎖的,保證 binlog 與 redo log 寫入順序是一致的。

總結(jié)下來,這套優(yōu)化主要帶來了兩個(gè)好處:

Commit 階段流水化作業(yè),stage 內(nèi)批處理,stage 之間可以并發(fā),大大提升了寫的并發(fā)度,進(jìn)而提高吞吐與資源利用率。

redo log / binlog 合并刷盤,大幅減少 IO 次數(shù)。

3. 代碼實(shí)現(xiàn)

3.1 Prepare

協(xié)調(diào)者的 Prepare 調(diào)用存儲引擎的 ha_prepare_low 即可,下面這段注釋說的很清楚,此時(shí)不持久化 InnoDB redo log。

int MYSQL_BIN_LOG::prepare(THD *thd, bool all) {

/*

Set HA_IGNORE_DURABILITY to not flush the prepared record of the

transaction to the log of storage engine (for example, InnoDB

redo log) during the prepare phase. So that we can flush prepared

records of transactions to the log of storage engine in a group

right before flushing them to binary log during binlog group

commit flush stage. Reset to HA_REGULAR_DURABILITY at the

beginning of parsing next command.

*/

thd->durability_property = HA_IGNORE_DURABILITY;

int error = ha_prepare_low(thd, all);

return error;

}

3.2 Commit

組提交的代碼主要位于 MYSQL_BIN_LOG::ordered_commit

MySQL 8.0.29 后將原來 slave 并行回放過程抽象成新的 stage0(原來這個(gè)流程也是有的,只是沒有抽象為 stage0),其工作是協(xié)調(diào)多個(gè)回放線程的回放順序,讓事務(wù)提交順序與主庫一致。以下代碼只有備庫回放會走到。

/*

Stage #0: ensure slave threads commit order as they appear in the slave's

relay log for transactions flushing to binary log.


This will make thread wait until its turn to commit.

Commit_order_manager maintains it own queue and its own order for the

commit. So Stage#0 doesn't maintain separate StageID.

*/

if (Commit_order_manager::wait_for_its_turn_before_flush_stage(thd) ||

ending_trans(thd, all) ||

Commit_order_manager::get_rollback_status(thd)) {

if (Commit_order_manager::wait(thd)) {

return thd->commit_error;

}

}

stage 轉(zhuǎn)換函數(shù)

事務(wù)提交三個(gè) stage 之間的轉(zhuǎn)換,都用的是 MYSQL_BIN_LOG::change_stage 函數(shù),其主要邏輯是調(diào)用了 Commit_stage_manager::enroll_for。該函數(shù)在 8.0.29 版本里,加了很多WL#7846 處理邏輯,幫助備庫在不開 binlog,但是并行回放的情況下,依舊可以和主庫保持相同的提交序,這一部分我會從下面的核心代碼里刪除,感興趣的朋友可以看下WL#7846 。

enroll_for 主要做了以下幾件事:

1.判斷自己是不是入隊(duì)的第一個(gè),如果是則為 leader,否則為 follower,enroll_for 的返回值為 true 則為 leader。

2.釋放上個(gè)階段持有的鎖,先入隊(duì)新的 stage,再釋放上一個(gè) stage 的鎖,保證事務(wù)執(zhí)行的順序在每個(gè) stage 相同,保證事務(wù)的正確性。注意:BINLOG_FLUSH_STAGE 沒有上一個(gè)階段的鎖,入?yún)?stage_mutex 為 nullptr。

3.follower 會阻塞等待在 m_stage_cond_binlog 條件變量上。

4.Leader 持有本階段的鎖(enter_mutex)。

bool MYSQL_BIN_LOG::change_stage(THD *thd [[maybe_unused]],

Commit_stage_manager::StageID stage,

THD *queue, mysql_mutex_t *leave_mutex,

mysql_mutex_t *enter_mutex) {

if (!Commit_stage_manager::get_instance().enroll_for(

stage, queue, leave_mutex, enter_mutex)) {

return true;

}

return false;

}


bool Commit_stage_manager::enroll_for(StageID stage, THD *thd,

mysql_mutex_t *stage_mutex,

mysql_mutex_t *enter_mutex) {



// 1.判斷自己是不是入隊(duì)的第一個(gè),如果是則為 leader,

// 否則為 follower,enroll_for 的返回值為 true 則為 leader。

lock_queue(stage);

bool leader = m_queue[stage].append(thd);

unlock_queue(stage);



// 2.先入隊(duì)新的 stage,再釋放上一個(gè) stage 的鎖,

// 保證事務(wù)執(zhí)行的順序在每個(gè) stage 相同,保證事務(wù)的正確性。

// 注意:BINLOG_FLUSH_STAGE 沒有上一個(gè)階段的鎖,入?yún)?stage_mutex 為 nullptr。



// 特殊情況:當(dāng)前持有的是 LOCK_log,且正在進(jìn)行 rotating,就不用釋放當(dāng)前 stage 的鎖了

// 因?yàn)?rotating 需要 LOCK_log

bool need_unlock_stage_mutex =

!(mysql_bin_log.is_rotating_caused_by_incident &&

stage_mutex == mysql_bin_log.get_log_lock());


if (stage_mutex && need_unlock_stage_mutex) mysql_mutex_unlock(stage_mutex);


// 3.follower 會阻塞等待在 m_stage_cond_binlog 條件變量上。

if (!leader) {

mysql_mutex_lock(&m_lock_done);

while (thd->tx_commit_pending) {

mysql_cond_wait(&m_stage_cond_binlog, &m_lock_done);

}

mysql_mutex_unlock(&m_lock_done);

return false;

}


// 4.leader 持有本階段的鎖(enter_mutex)。

bool need_lock_enter_mutex = false;

if (leader && enter_mutex != nullptr) {

// 特殊情況:enter_mutex 是 LOCK_log,且正在進(jìn)行 rotating,就不用再去加鎖了,

// 因?yàn)橐呀?jīng)加上了。

need_lock_enter_mutex = !(mysql_bin_log.is_rotating_caused_by_incident &&

enter_mutex == mysql_bin_log.get_log_lock());

if (need_lock_enter_mutex)

mysql_mutex_lock(enter_mutex);

else

mysql_mutex_assert_owner(enter_mutex);

}

return leader;

}

Stage 1 -- BINLOG_FLUSH_STAGE

事務(wù) flush 到 binlog (不 sync) ,代碼中的解釋:

/*

Stage #1: flushing transactions to binary log


While flushing, we allow new threads to enter and will process

them in due time. Once the queue was empty, we cannot reap

anything more since it is possible that a thread entered and

appointed itself leader for the flush phase.

*/

BINLOG_FLUSH_STAGE leader 的主要工作如下(代碼依舊位于MYSQL_BIN_LOG::ordered_commit)

1.change_stage,進(jìn)入 BINLOG_FLUSH_STAGE 狀態(tài)。

2.如果發(fā)現(xiàn) binlog 被關(guān)了,直接跳到(goto)commit stage。

(3,4,5 在 process_flush_stage_queue 完成)

3.拿到 flush queue 的 head,清空 flush queue,以便新的線程進(jìn)入作為 leader。調(diào)用 ha_flush_logs(true) 批量刷 redo log。

4.依次調(diào)用 MYSQL_BIN_LOG::flush_thread_caches 將每個(gè)事務(wù)緩存在 binlog_cache_mngr 里的信息 flush 到 binlog(cache)。調(diào)用路徑:

MYSQL_BIN_LOG::flush_thread_caches

|--binlog_cache_mngr::flush

|----binlog_stmt_cache_data::flush

|----binlog_trx_cache_data::flush

|------binlog_cache_data::flush
|--------MYSQL_BIN_LOG::write_transaction

5.判斷是否需要 rotate。

6.將 binlog 寫到 binlog 文件(不 sync),flush_cache_to_file

// 1. change_stage,進(jìn)入 BINLOG_FLUSH_STAGE 狀態(tài).

if (change_stage(thd, Commit_stage_manager::BINLOG_FLUSH_STAGE, thd, nullptr,

&LOCK_log)) {

return finish_commit(thd);

}

// 2.如果 binlog 被關(guān)了,直接跳到(goto)COMMIT_STAGE。

// leave_mutex_before_commit_stage 表示需要在 COMMIT_STAGE 釋放的鎖。

if (unlikely(!is_open())) {

final_queue = fetch_and_process_flush_stage_queue(true);

leave_mutex_before_commit_stage = &LOCK_log;

goto commit_stage;

}

// 3/4/5 步在該函數(shù)執(zhí)行

flush_error = process_flush_stage_queue(&total_bytes, &do_rotate, &wait_queue);

if (flush_error == 0 && total_bytes > 0) {

// 6.將 binlog 寫到 binlog 文件

flush_error = flush_cache_to_file(&flush_end_pos);

}

process_flush_stage_queue 執(zhí)行 3-5 步,事務(wù) redo 刷盤,將事務(wù)的信息寫到 binary log

int MYSQL_BIN_LOG::process_flush_stage_queue(my_off_t *total_bytes_var,

bool *rotate_var,

THD **out_queue_var) {

// 3. 該函數(shù)會調(diào)用 ha_flush_logs 持久化 redo log

THD *first_seen = fetch_and_process_flush_stage_queue(should_return, term, true);

// 4.依次將所有所有事務(wù)從各自的 cache 里 flush 到 binlog

for (THD *head = first_seen; head; head = head->next_to_commit) {

std::pair result = flush_thread_caches(head);;

}

// 5.判斷是否需要 rotate。剛寫完 binlog,是判斷的恰當(dāng)時(shí)期。

if (total_bytes > 0 &&

(m_binlog_file->get_real_file_size() >= (my_off_t)max_size ||

DBUG_EVALUATE_IF("simulate_max_binlog_size", true, false)))

*rotate_var = true;

return flush_error;

}

Stage 2 -- SYNC_STAGE

BINLOG_FLUSH_STAGE 階段的 leader 帶著一個(gè)鏈表進(jìn)入 SYNC_STAGE 階段,首先依舊調(diào)用 change_state 函數(shù),可能成為該階段的 leader,也可能成為 follower,因?yàn)榇藭r(shí) LOCK_sync 可能正在被做 sync 的線程持有。多個(gè) flush queue 會因?yàn)榈却i而合并成一個(gè) sync queue。

Sync 的后續(xù)流程:

1.判斷本次要不要 sync

一個(gè) SYNC_STAGE 的 leader 通過參數(shù)判斷,本次是否需要 sync。sync_counter 變量代表進(jìn)入 SYNC_STAGE 但是沒有真正 sync 的 leader 的個(gè)數(shù)。當(dāng) MySQL 配置參數(shù) sync_binlog 設(shè)置大于 1 時(shí),并不是每個(gè) leader 執(zhí)行到這里都會 sync。

get_sync_period() 獲得的值,即是 sync_binlog 參數(shù)的值。

因此,判斷 sync_counter + 1 >= get_sync_period(),表示當(dāng)前的 leader 可以 sync 了,那么該線程繼續(xù)等一會兒,等待更多的線程進(jìn)入 sync queue 在一起提交,具體等多久,由 opt_binlog_group_commit_sync_no_delay_count 和 opt_binlog_group_commit_sync_delay 決定。如果本 leader 不 sync,則不用等待。

注意,當(dāng) sync_binlog == 0 時(shí),每個(gè) leader 線程都要等待。當(dāng) sync_binlog == 1 時(shí),同樣每個(gè) leader 線程都要等待,因?yàn)槊總€(gè) leader 都要 sync。當(dāng) sync_binlog > 1 時(shí),一部分 leader 線程就不用等待,接著執(zhí)行,反正也不會 sync。

2.調(diào)用 sync_binlog_file 去 sync binlog。sync_binlog_file 中實(shí)現(xiàn)只有當(dāng) sync_period > 0 && ++sync_counter >= sync_period 時(shí)才真正 sync。

/*
Stage #2: Syncing binary log file to disk
*/

if (change_stage(thd, Commit_stage_manager::SYNC_STAGE, wait_queue, &LOCK_log,
&LOCK_sync)) {
return finish_commit(thd);
}

// 1.判斷本次要不要真正 sync,真正 sync 需要等一會,詳見上文的說明
if (!flush_error && (sync_counter + 1 >= get_sync_period()))
Commit_stage_manager::get_instance().wait_count_or_timeout(
opt_binlog_group_commit_sync_no_delay_count,
opt_binlog_group_commit_sync_delay, Commit_stage_manager::SYNC_STAGE);

// 僅是后面更新 binlog end 位點(diǎn)用
final_queue = Commit_stage_manager::get_instance().fetch_queue_acquire_lock(
Commit_stage_manager::SYNC_STAGE);

// 2. sync
if (flush_error == 0 && total_bytes > 0) {
std::pair result = sync_binlog_file(false);
sync_error = result.first;
}

Stage 3 -- COMMIT_STAGE

依次將 redolog 中已經(jīng) prepare 的事務(wù)在引擎層提交,該階段不用刷盤,因?yàn)?flush 階段中的 redolog 刷盤已經(jīng)足夠保證數(shù)據(jù)庫崩潰時(shí)的數(shù)據(jù)安全了。

COMMIT_STAGE 的主要工作包括:

1.達(dá)成多數(shù)派后,調(diào)用 ha_commit_low 提交,提交完成后還需減少 prepared XID counter

2.喚醒所有等待的 follower,完成提交。

if ((opt_binlog_order_commits || Clone_handler::need_commit_order()) &&

(sync_error == 0 || binlog_error_action != ABORT_SERVER)) {

if (change_stage(thd, Commit_stage_manager::COMMIT_STAGE, final_queue,

leave_mutex_before_commit_stage, &LOCK_commit)) {

return finish_commit(thd);

}

THD *commit_queue =

Commit_stage_manager::get_instance().fetch_queue_acquire_lock(

Commit_stage_manager::COMMIT_STAGE);



// 運(yùn)行 after sync hook(如果有的話)

if (flush_error == 0 && sync_error == 0)

sync_error = call_after_sync_hook(commit_queue);


// 1.在該函數(shù)內(nèi)完成,調(diào)用 ha_commit_low 提交引擎

process_commit_stage_queue(thd, commit_queue);

mysql_mutex_unlock(&LOCK_commit);



// 運(yùn)行 after commit hook(如果有的話)

process_after_commit_stage_queue(thd, commit_queue);

final_queue = commit_queue;

} else {

// 如果因?yàn)?opt_binlog_order_commits 為 false 進(jìn)入這里。

// 不 ordered commit,那么就等 follower 被通知后,自己去提交

if (leave_mutex_before_commit_stage)

mysql_mutex_unlock(leave_mutex_before_commit_stage);

if (flush_error == 0 && sync_error == 0)

sync_error = call_after_sync_hook(final_queue);

}



// 3. 通知隊(duì)列中所有等待的線程

// follower 如果發(fā)現(xiàn)事務(wù)沒有提交,會調(diào)用 ha_commit_low, 此時(shí)就不能保證 commit 的順序了。

/* Commit done so signal all waiting threads */

Commit_stage_manager::get_instance().signal_done(final_queue);

網(wǎng)站欄目:MySQLBinlog組提交實(shí)現(xiàn)
網(wǎng)頁URL:http://www.dlmjj.cn/article/djihgoi.html