標籤:

MySQL 5.7中sync

來自:

gaopengtttt,作者:高鵬(重慶八怪)

鏈接:https://www.jianshu.com/p/59c6ecb46fe5

本文為我的一些零散記錄供以後參考,本來知道已經很久了但是有朋友問到老是需要翻很久,這裡乾脆記錄下來,但是水平有限都不深入,如有誤導請見諒為什麼將他們放在一起討論因為他們都存在於同一個函數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 = 1 這裡才發送信號給dump線程通知進行發送binlog。

同時如果我們翻開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 

0

x7f44607aa700 (LWP 

24897

)):

#0

  

0

x00007f4475b02a5e 

in

 pthread_cond_timedwait@@GLIBC_2.

3.2

 () from /lib64/libpthread.so.

0

#1

  

0

x00007f44603e35d3 

in

 ReplSemiSyncMaster::commitTrx(char 

const

*, unsigned long long) () from /usr/

local

/mysql/lib/plugin/semisync_master.so

#2

  

0

x0000000000c8197a 

in

 Binlog_storage_delegate::after_sync(THD*, char 

const

*, unsigned long long) ()

#3

  

0

x0000000000edd46b 

in

 call_after_sync_hook(THD*) ()

#4

  

0

x0000000000eed935 

in

 MYSQL_BIN_LOG::ordered_commit(THD*, bool, bool) ()

#5

  

0

x0000000000eedf55 

in

 MYSQL_BIN_LOG::commit(THD*, bool) ()

#6

  

0

x000000000081e494 

in

 ha_commit_trans(THD*, bool, bool) ()

#7

  

0

x0000000000dce032 

in

 trans_commit_stmt(THD*) ()

#8

  

0

x0000000000d134e7 

in

 mysql_execute_command(THD*, bool) ()

  • 等待LOCK_commit mutex線程

Thread 

6

 (Thread 

0

x7f4460769700 (LWP 

25017

)):

#0

  

0

x00007f4475b05334 

in

 __lll_lock_wait () from /lib64/libpthread.so.

0

#1

  

0

x00007f4475b0060e 

in

 _L_lock_995 () from /lib64/libpthread.so.

0

#2

  

0

x00007f4475b00576 

in

 pthread_mutex_lock () from /lib64/libpthread.so.

0

#3

  

0

x0000000000eed31f 

in

 MYSQL_BIN_LOG::change_stage(THD*, Stage_manager::StageID, THD*, st_mysql_mutex*, st_mysql_mutex*) ()

#4

  

0

x0000000000eed5e8 

in

 MYSQL_BIN_LOG::ordered_commit(THD*, bool, bool) ()

#5

  

0

x0000000000eedf55 

in

 MYSQL_BIN_LOG::commit(THD*, bool) ()

#6

  

0

x000000000081e494 

in

 ha_commit_trans(THD*, bool, bool) ()

#7

  

0

x0000000000dce032 

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極端情況下可能引擎層已經提交完成,事物對主庫可見,但是從庫還沒有傳輸完成如果從庫奔潰可能出現少事物的情況。

結語

對於5.7中安全的設置應該盡量保證sync_binlog=1同時設置rpl_semi_sync_master_wait_point為after_sync,這實際上都是默認設置。


●編號403,輸入編號直達本文

●輸入m獲取文章

目錄

推薦↓↓↓

 

Web開發

更多推薦

18個技術類微信公眾號

涵蓋:程序人生、演算法與數據結構、黑客技術與網路安全、大數據技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。


推薦閱讀:

黃河之水天上來,數據之源資料庫——SQL
mac安裝mysql的兩種方法(含配置)
mysql資料庫基礎操作大全(小白必看)
MySQL 並行複製演進及 MySQL 8.0 中基於 WriteSet 的優化
InnoDB 存儲引擎原理解析

TAG:MySQL |