大家都知道在mysql中,在事務真正commit之前,會将事務的binlog日志寫入到binlog檔案中。在mysql的5.7版本中,提供了所謂的無損複制功能,該功能的作用就是在主庫的事務對其他的會話線程可見之前,就将該事務的日志同步到從庫,保證了事務可以安全地無丢失地複制到從庫。
下面我們從源碼來分析mysql的事務送出以及事務在何時将binlog複制到從庫的。
mysql_bin_log::ordered_commit,這個是事務在binlog階段送出的核心函數,通過該函數,實作了事務日志寫入binlog檔案,以及觸發dump線程将binlog發送到slave,在最後的步驟,将事務設定為送出狀态。
我們來分析mysql_bin_log::ordered_commit這個函數的核心過程,該函數位于binlog.cc檔案中。
源碼分析
mysql_bin_log::ordered_commit,這個函數,核心步驟如下:
第一步驟:flush
stage#1: flushing transactions to binary log:
步驟1 :将事務的日志寫入binlog檔案的buffer中,函數如下:
process_flush_stage_queue(&total_bytes,&do_rotate, &wait_queue);
從5.6開始,mysql引入了group commit的概念,這樣可以避免每個事務送出都會鎖定一次binlog。
另外,還有一個用處,就是mysql5.7的基于logical_clock的并行複制。在一個組裡面(其實是一個隊列),這一組隊列的頭事務是相同的,是以這一組事務的last_committed(上一組的最後一個送出的事務)的事務也是同一個。我們都知道,last_committed相同的事務,是可以在從庫并行relay(重演)的。
該函數process_flush_stage_queue的作用,就是将commit隊列中的線程一個一個地取出,然後執行子函數 flush_thread_caches(head);循環的代碼如下:将各自線程中的binlog cache寫入到binlog中。
/* flush thread caches to binary log. */
for (thd *head= first_seen ; head ; head = head->next_to_commit)
{
std::pairresult= flush_thread_caches(head);,my_off_t>
total_bytes+= result.second;
if(flush_error == 1)
flush_error= result.first;
#ifndef dbug_off
no_flushes++;
#endif
}
第二步驟:sync to disk
stage#2: syncing binary log file to disk
第二步:将binlog file中cache的部分寫入disk.但這個步驟參數sync_binlog起決定性的作用。
我們來看看源碼,除了這些還有哪些細節步驟,聽完源碼分析之後,你應該有新的收獲與了解。在執行真正的将binlog寫到磁盤之前,會進行一個等待,函數如下:
stage_manager.wait_count_or_timeout(opt_binlog_group_commit_sync_no_delay_count,
opt_binlog_group_commit_sync_delay,
stage_manager::sync_stage);
等待的時間由mysql參數檔案中的binlog_group_commit_sync_delay,binlog_group_commit_sync_no_delay_count 這兩參數共同決定。第一個表示該事務組送出之前總共等待累積到多少個事務,第二個參數則表示該事務組總共等待多長時間後進行送出,任何一個條件滿足則進行後續操作。
因為有這個等待,可以讓更多事務的binlog通過一次寫binlog檔案磁盤來完成送出,進而獲得更高的吞吐量。
接下來,就是執行sync_binlog_file,該函數會用到mysql參數檔案中sync_binlog參數的值,如果為0,則不進行寫磁盤操作,由作業系統決定什麼時候刷盤,如果為1,則強制進行寫磁盤操作。
再接下來,執行update_binlog_end_pos函數,用來更新binlog檔案的最後的位置binlog_end_pos,該binlog_end_pos是一個全局的變量。在執行更新該位置之前,先得找到最後一個送出事務的線程(因為是group commit,多個事務排隊送出的機制)。因為已經将要送出事務的線程組成了一個連結清單,是以通過從頭到尾找,可以找到最後一個線程。代碼如下:
if(update_binlog_end_pos_after_sync)
thd*tmp_thd= final_queue;
while(tmp_thd->next_to_commit != null)
tmp_thd= tmp_thd->next_to_commit;
update_binlog_end_pos(tmp_thd->get_trans_pos());
接下來,我們來看一下這個函數update_binlog_end_pos。這個函數很簡單,傳入一個pos,然後将其指派給全局變量binlog_end_pos,接下來就是最核心的一行代碼,signal_update(),發送binlog更新的信号。是以從主庫同步binlog到從庫的dump線程,會接收到這個binlog已有更新的信号,然後啟動dump binlog的流程。
函數update_binlog_end_pos的完整代碼如下:
void update_binlog_end_pos(my_off_tpos)
lock_binlog_end_pos();
if (pos >binlog_end_pos)
binlog_end_pos= pos;
signal_update();
unlock_binlog_end_pos();
semi-sync
通過上面的步驟介紹,我們可以看到在binlog檔案的最新位置更新的時候,就已經通過signal_update函數發送信号給binlog的dump線程,該線程就可以将事務的binlog同步到從庫,從庫接收到日志之後,就可以relay日志,實作了主從同步。
是以,再次重複說明一下,按照上面的解釋,在事務真正送出完成之前就開始發送了binlog已經更新的信号,dump線程收到信号,即可以進行binlog的同步。那semisync的作用是什麼呢?
實際上,有沒有semisync機制,對上面介紹的mysql的有關事務送出中關于binlog的流程都是一樣的。semisync的作用,隻是主從之間的一個确認過程,主庫等待從庫傳回相關位置的binlog已經同步到從庫的确認(而實際實作則是等待dump線程給使用者會話線程一個回複),沒有得到确認之前(或者等待時間達到timeout),事務送出則在該函數(步驟)上等待直至獲得傳回。
具體執行binlog已經同步到某個位置的的确認函數為repl_semi_report_binlog_sync,函數如下:
intrepl_semi_report_binlog_sync(binlog_storage_param *param,
constchar *log_file,
my_off_t log_pos)
if(rpl_semi_sync_master_wait_point == wait_after_sync)
returnrepl_semisync.committrx(log_file, log_pos);
return 0;
通過觀察上述函數,我們可以看到有個rpl_semi_sync_master_wait_point變量與wait_after_sync比較,如果不相等,則直接傳回,直接傳回則表示不需要在此時此刻确認binlog是否已經同步。而這個變量的取值來自于半同步參數semi_sync_master_wait_point的初始設定,我們可以設定為after_sync與after_commit。
這兩個參數含義的差別是:after_sync是在将binlog sync到disk之後(具體是否真正sync由參數sync_binlog的值決定)進行日志同步确認,而after_commit是将事務完成在innodb裡面送出之後再進行binlog的同步确認。兩者确認的時間點不同,after_sync要早于after_commit。
接下來,我們來看repl_semisync.committrx 這個函數,這個函數有兩個傳入參數,一個是binlog檔案,一個binlog檔案的位移。我們來看這個函數的含義吧。算了,還是直接用源碼的注釋來解釋吧。

上面的注釋說得相當清楚,就是該commitrx函數會等待binlog-dump傳回已經同步到該位置的報告,如果還沒有同步到該位置,則繼續等待,直到逾時傳回。
當會話線程收到該函數的傳回時,事務的送出過程繼續往下走,直至在innodb真正送出。
總結
通過上述對mysql的事務送出過程中的前段分析,應該可以了解semi-sync的同步機制與異步機制的差別。
semi-sync的主從同步機制與異步機制在同步的處理方式上無任何差別,唯一的差別就是semi-sync在事務送出中段(假如設定為after_sync)或者送出後的階段(after_commit), 有一個驗證該事務涉及的binlog是否已經同步到從庫。而這個同步驗證,會拉長整個事務的送出時間,因為事務送出在資料庫中幾乎是串行(如果按group commit為一個機關,就算是完全地串行),這是影響mysql吞吐量的關鍵點,當這個關鍵點被拉長,對全局的影響就被放大。雖然僅僅多了這麼一個确認的動作,但主庫處于semi-sync的同步狀态與異步狀态的吞吐量相比,相差了好幾倍。
上述解釋就是其真正的原因。
大家都知道在mysql中,在事務真正commit之前,會将事務的binlog日志寫入到binlog檔案中。在mysql的5.7版本中,提供了所謂的無損複制功能,該功能的作用就是在主庫的事務對其他的會話線程可見之前,就将該事務的日志同步到從庫,保證了事務可以安全地無丢失地複制到從庫。

原文釋出時間為:2017-04-16
本文來自雲栖社群合作夥伴dbaplus