MySQL5.7效能與資料安全大幅提升的緣由
大家都知道在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.另外,還有一個用處,就是mysql的5.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::pair<int,my_off_t>result= flush_thread_caches(head);
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的完整程式碼如下。
semisync
通過上面的步驟介紹,我們看到,在binlog檔案的最新位置更新的時候,就已經通過signal_update函式傳送訊號給binlog的dump執行緒,該執行緒就可以將事務的binlog同步到從庫,從庫接收到日誌之後,就可以relay日誌,實現了主從同步。因此,再次重複說明一下,按照上面的解釋,在事務真正提交完成之前就開始傳送了binlog已經更新的訊號,dump執行緒收到訊號,即可以進行binlog的同步。而semisync的作用是什麼呢?
實際上,有沒有semisync機制,上面介紹的mysql的有關事務提交中關於binlog的流程都是一樣的,semisync的作用,只是主從之間的一個確認過程,主庫等待從庫返回相關位置的binlog已經同步到從庫的確認,(而實際實現則是等待dump執行緒給使用者會話執行緒一個回覆),沒有得到確認之前(或者等待時間達到timeout),事務提交則在該函式(步驟)上等待直至獲得返回。具體執行binlog已經同步到某個位置的的確認函式為repl_semi_report_binlog_sync,函式如下:
int repl_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)
return repl_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的同步狀態與非同步狀態的吞吐量相比,相差了好幾倍。上述解釋就是其真正的原因。
部分歷史文章:
文章來自微信公眾號:資料庫隨筆