MySQL 5.7中sync_binlog引數和半同步中after_commit和after_sync的區別

gaopengtttt發表於2018-02-24

本文為我的一些零散記錄供以後參考,本來知道已經很久了但是有朋友問到老是需要翻很久,這裡乾脆記錄下來,但是水平有限都不深入,如有誤導請見諒為什麼將他們放在一起討論因為他們都存在於同一個函式MYSQL_BIN_LOG::ordered_commit函式中。
程式碼版本:percona 5.7.14


以下討論sync_binlog引數在5.7中的作用


一、sync_binlog引數設定在原始碼中的表示


這個引數大家都知道控制著binlog的刷盤時機,但是在5.7中其還有另外一個功能,我這裡將解釋他的兩個功能。我摘取了原始碼中說明問題的部分進行展示如下:


flush階段:
flush_error= process_flush_stage_queue(&total_bytes, &do_rotate,&wait_queue);//進行binlog的從binlog buffer或者臨時檔案寫入到binlog檔案(注意是寫到kernel buffer還沒做fsync),同時觸發innodb的組提交邏輯,innodb組提交的邏輯程式碼是阿里的印風兄寫的,我請教過他。
update_binlog_end_pos_after_sync= (get_sync_period() == 1);//sync_binlog引數 如果為1則為真如果不為1則為假
    if (!update_binlog_end_pos_after_sync)//如果sync_binlog=1則 這裡不發訊號給dump 如果不是1則發訊號進行dump
      update_binlog_end_pos();
其中get_sync_period()函式返回就是sync_binlog的設定,這裡能夠清晰看到如果sync_binlog != 1才會 在flush階段傳送訊號給dump執行緒。


sync階段
if (flush_error == 0 && total_bytes > 0) //這裡進行sync binlog,
  {
    DEBUG_SYNC(thd, "before_sync_binlog_file");
    std::pair<bool, bool> result= sync_binlog_file(false);
    sync_error= result.first;
  }


  if (update_binlog_end_pos_after_sync) //如果sync_binlog = 1 這裡才傳送訊號給dump執行緒通知進行傳送binlog
  {
    THD *tmp_thd= final_queue;


    while (tmp_thd->next_to_commit != NULL)
      tmp_thd= tmp_thd->next_to_commit;
    if (flush_error == 0 && sync_error == 0)
      update_binlog_end_pos(tmp_thd->get_trans_pos());
  }
如果我們翻開sync_binlog_file函式的邏輯會發現這樣一個邏輯:


if (force || (sync_period && ++sync_counter >= sync_period))
  {
    sync_counter= 0;
很顯然這裡有一個計數器sync_counter,如果當sync_binlog>1的時候才,等到sync_counter大於你設定的sync_binlog的值的時候才會觸發fsync binlog(注意這裡是++sync_counter 先自增再比較),這裡也解釋了sync_binlog>1的時候代表的是什麼值,代表是組提交的次數。


二、sync_binlog引數在5.7中作用的總結


sync_binlog=0:binlog不FSYNC刷盤,依賴於OS刷盤機制,同時dump執行緒會在flush階段後進行binlog傳輸
sync_binlog=1:binlog進行FSYNC刷盤,同時dump執行緒會在sync階段後進行binlog傳輸
sync_binlog>1:binlog將在指定次陣列提交後FSYNC刷盤,同時dump執行緒會在flush階段後進行binlog傳輸
三、為什麼這麼修改


這也是一個朋友問我的問題,如果主庫異常重啟後,從庫是否有比主庫多事物的風險,實際上這個問題就是到底在什麼階段後dump執行緒進行傳輸binlog的問題。實際上如果在flush階段過後傳輸確實可能出現這個問題,而在sync階段後傳輸這個時候binlog已經落盤了,就不會有這種風險了。如果出現這種錯誤會報錯如下,這個錯誤也是有朋友遇到過的:


ER_SLAVE_HAS_MORE_GTIDS_THAN_MASTER 
"Slave has more GTIDs than the master has, using the master's SERVER_UUID. This may indicate that the end of the binary log was truncated or that the last binary log file was lost, e.g., after a power or disk failure when sync_binlog != 1. The master may or may not have rolled back transactions that were already replicated to the slave. Suggest to replicate any transactions that master has rolled back from slave to master, and/or commit empty transactions on master to account for transactions that have been committed on master but are not included in GTID_EXECUTED." 
下面開始討論半同步中after_commit和after_sync的區別,這個問題一直也是大家討論的重點。


四、從一個問題出發討論


也是有很多朋友問我,其中一個問題如下:


半同步:after_sync模式
測試:超時時間設定為1個小時不讓主庫切換非同步,同時停掉slave的I/O執行緒,
主庫:
session1:插入一條資料hang住
session2:插入一條資料hang住
session3:插入一條資料hang住
其中session1狀態為等待ACK,其他session狀態為query end
問為什麼其他session狀態不是等待ACK而是query end。如果是after_commit模式則全部是等待ACK狀態


實際上拿到這位朋友的pstack後大概就能確認大概是什麼問題如下:


等待ACK執行緒:
Thread 7 (Thread 0x7f44607aa700 (LWP 24897)):
#0  0x00007f4475b02a5e in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1  0x00007f44603e35d3 in ReplSemiSyncMaster::commitTrx(char const*, unsigned long long) () from /usr/local/mysql/lib/plugin/semisync_master.so
#2  0x0000000000c8197a in Binlog_storage_delegate::after_sync(THD*, char const*, unsigned long long) ()
#3  0x0000000000edd46b in call_after_sync_hook(THD*) ()
#4  0x0000000000eed935 in MYSQL_BIN_LOG::ordered_commit(THD*, bool, bool) ()
#5  0x0000000000eedf55 in MYSQL_BIN_LOG::commit(THD*, bool) ()
#6  0x000000000081e494 in ha_commit_trans(THD*, bool, bool) ()
#7  0x0000000000dce032 in trans_commit_stmt(THD*) ()
#8  0x0000000000d134e7 in mysql_execute_command(THD*, bool) ()
等待LOCK_commit mutex執行緒
Thread 6 (Thread 0x7f4460769700 (LWP 25017)):
#0  0x00007f4475b05334 in __lll_lock_wait () from /lib64/libpthread.so.0
#1  0x00007f4475b0060e in _L_lock_995 () from /lib64/libpthread.so.0
#2  0x00007f4475b00576 in pthread_mutex_lock () from /lib64/libpthread.so.0
#3  0x0000000000eed31f in MYSQL_BIN_LOG::change_stage(THD*, Stage_manager::StageID, THD*, st_mysql_mutex*, st_mysql_mutex*) ()
#4  0x0000000000eed5e8 in MYSQL_BIN_LOG::ordered_commit(THD*, bool, bool) ()
#5  0x0000000000eedf55 in MYSQL_BIN_LOG::commit(THD*, bool) ()
#6  0x000000000081e494 in ha_commit_trans(THD*, bool, bool) ()
#7  0x0000000000dce032 in trans_commit_stmt(THD*) ()
這裡就很明顯其他提交事物(這裡指的是 Thread 6)堵塞在了MYSQL_BIN_LOG::change_stage函式上,其作用正是獲取某個階段的Mutex。而本執行緒(這裡指的是 Thread 7)則是在ReplSemiSyncMaster::commitTrx上堵塞在某個Mutex上。


五、after_commit和after_sync的程式碼位置和區別


這裡直接用程式碼說明進行給出,當然我只是提取了說明問題的程式碼片段:


commit階段:
1、 change_stage(thd, Stage_manager::COMMIT_STAGE,final_queue, leave_mutex_before_commit_stage,&LOCK_commit))//持有LOCK_commit mutext進入commit階段
2、 sync_error= call_after_sync_hook(commit_queue);//這裡呼叫after sync hook 其在LOCK_commit保護下 此時還沒有做引擎層commit
3、 process_commit_stage_queue(thd, commit_queue);//進行引擎層提交操作,具體細節以後在研究
4、 mysql_mutex_unlock(&LOCK_commit);//這裡提交完成解鎖佇列
5、 stage_manager.signal_done(final_queue); //這裡喚醒全部本組堵塞在 flush階段的follower執行緒 分別做提交 但是如果是order commit 提交已經做完 這裡什麼都不需要做了
6、 (void) finish_commit(thd); //finish_commit會呼叫 atfer commit hook 其不在LOCK_commit保護下
如果拋開程式碼總結如下:


1、leader 持有LOCK_commit 鎖 進入 commit階段。
2、如果是設定after_sync,使用after sync 掛鉤來確認ack 。
3、進行引擎層提交,完成後解鎖LOCK_commit 鎖。
4、喚醒所有 follwer執行緒。
5、如果設定是after_commit,使用after commit 掛鉤來確認ack 。
這裡我們可以清楚的看到,他們的區別,實際上正如其名字一樣就是說到底在那個步驟進行日誌傳輸完成的確認,是在實際引擎層提交之前還是之後,如果是在之前則在mutex LOCK_commit的保護下,如果是在之後則不需要持有LOCK_commit mutex,這也是為什麼會出現上面那個堵塞案例的原因。在5.7中預設是after_sync設定為after_sync後顯然更加安全,如果是after_commit極端情況下可能引擎層已經提交完成,事物對主庫可見,但是從庫還沒有傳輸完成如果從庫奔潰可能出現少事物的情況。


作者微信:


MySQL 5.7中sync_binlog引數和半同步中after_commit和after_sync的區別

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7728585/viewspace-2151218/,如需轉載,請註明出處,否則將追究法律責任。

相關文章