MySQL主備、主從、讀寫分離詳解

一、MySQL主備的基本原理

在這裏插入圖片描述
在狀態1中,客戶端的讀寫都直接訪問節點A,而節點B是A的備庫,只是將A的更新都同步過來,到本地執行。這樣可以保持節點B和A的數據是相同的。當需要切換的時候,就切成狀態2。這時候客戶端讀寫訪問的都是節點B,而節點A是B的備庫

在狀態1中,雖然節點B沒有被直接訪問,但是建議把備庫節點B,設置成只讀模式。有以下幾個原因:

1.有時候一些運營類的查詢語句會被放到備庫上去查,設置爲只讀可以防止誤操作

2.防止切換邏輯有bug

3.可以用readonly狀態,來判斷節點的角色

把備庫設置成只讀,還怎麼跟主庫保持同步更新?

readonly設置對超級權限用戶是無效的,而用於同步更新的線程,就擁有超級權限

下圖是一個update語句在節點A執行,然後同步到節點B的完整流程圖:
在這裏插入圖片描述
備庫B和主庫A之間維持了一個長連接。主庫A內部有一個線程,專門用於服務備庫B的這個長連接。一個事務日誌同步的完整過程如下:

1.在備庫B上通過change master命令,設置主庫A的IP、端口、用戶名、密碼,以及要從哪個位置開始請求binlog,這個位置包含文件名和日誌偏移量

2.在備庫B上執行start slave命令,這時備庫會啓動兩個線程,就是圖中的io_thread和sql_thread。其中io_thread負責與主庫建立連接

3.主庫A校驗完用戶名、密碼後,開始按照備庫B傳過來的位置,從本地讀取binlog,發給B

4.備庫B拿到binlog後,寫到本地文件,稱爲中轉日誌

5.sql_thread讀取中轉日誌,解析出日誌裏的命令,並執行

由於多線程複製方案的引入,sql_thread演化成了多個線程

二、循環複製問題

雙M結構:
在這裏插入圖片描述
節點A和節點B互爲主備關係。這樣在切換的時候就不用再修改主備關係

雙M結構有一個問題要解決,業務邏輯在節點A上更新了一條語句,然後再把生成的binlog發給節點B,節點B執行完這條更新語句後也會生成binlog。那麼,如果節點A同時是節點B的備庫,相當於又把節點B新生成的binlog拿過來執行了一次,然後節點A和B間,會不斷地循環執行這個更新語句,也就是循環複製

MySQL在binlog中記錄了這個命令第一次執行時所在實例的server id。因此,可以用下面的邏輯,來解決兩個節點間的循環複製問題:

1.規定兩個庫的server id必須不同,如果相同,則它們之間不能設定爲主備關係

2.一個備庫接到binlog並在重放的過程中,生成與原binlog的server id相同的新的binlog

3.每個庫在收到從自己的主庫發過來的日誌後,先判斷server id,如果跟自己的相同,表示這個日誌是自己生成的,就直接丟棄這個日誌

雙M結構日誌的執行流如下:

1.從節點A更新的事務,binlog裏面記的都是A的server id

2.傳到節點B執行一次以後,節點B生成的binlog的server id也是A的server id

3.再傳回給節點A,A判斷這個server id與自己的相同,就不會再處理這個日誌。所以,死循環在這裏就斷掉了

三、主備延遲

在這裏插入圖片描述

1、什麼是主備延遲?

與數據同步有關的時間點主要包括以下三個:

1.主庫A執行完成一個事務,寫入binlog,這個時刻記爲T1

2.之後傳給備庫B,備庫B接收完這個binlog的時刻記爲T2

3.備庫B執行完這個事務,把這個時刻記爲T3

所謂主備延遲,就是同一個事務,在備庫執行完成的時間和主庫執行完成的時間之間的差值,也就是T3-T1

可以在備庫上執行show slave status命令,它的返回結果裏面會顯示seconds_behind_master,用於表示當前備庫延遲了多少秒

seconds_behind_master的計算方法是這樣的:

1.每個事務的binlog裏面都有一個時間字段,用於記錄主庫上寫入的時間

2.備庫取出當前正在執行的事務的時間字段的值,計算它與當前系統時間的差值,得到seconds_behind_master

如果主備庫機器的系統時間設置不一致,不會導致主備延遲的值不準。備庫連接到主庫的時候,會通過SELECTUNIX_TIMESTAMP()函數來獲得當前主庫的系統時間。如果這時候發現主庫的系統時間與自己不一致,備庫在執行seconds_behind_master計算的時候會自動扣掉這個差值

網絡正常情況下,主備延遲的主要來源是備庫接收完binlog和執行完這個事務之間的時間差

主備延遲最直接的表現是,備庫消費中轉日誌的速度,比主庫生產binlog的速度要慢

2、主備延遲的原來

1.有些部署條件下,備庫所在機器的性能要比主庫所在的機器性能差

2.備庫的壓力大。主庫提供寫能力,備庫提供一些讀能力。忽略了備庫的壓力控制,導致備庫上的查詢耗費了大量的CPU資源,影響了同步速度,造成主備延遲

可以做以下處理:

  • 一主多從。除了備庫外,可以多接幾個從庫,讓這些從庫來分擔讀的壓力
  • 通過binlog輸出到外部系統,比如Hadoop這類系統,讓外部系統提供統計類查詢的能力

3.大事務。因爲主庫上必須等事務執行完纔會寫入binlog,再傳給備庫。所以,如果一個主庫上的語句執行10分鐘,那這個事務很可能會導致從庫延遲10分鐘

典型的大事務場景:一次性地用delete語句刪除太多數據和大表的DDL

四、主備切換策略

1、可靠性優先策略

雙M結構下,從狀態1到狀態2切換的詳細過程如下:

1.判斷備庫B現在的seconds_behind_master,如果小於某個值繼續下一步,否則持續重試這一步

2.把主庫A改成只讀狀態,即把readonly設置爲true

3.判斷備庫B的seconds_behind_master的值,直到這個值變成0爲止

4.把備庫B改成可讀寫狀態,也就是把readonly設置爲false

5.把業務請求切到備庫B
在這裏插入圖片描述
這個切換流程中是有不可用的時間的。在步驟2之後,主庫A和備庫B都處於readonly狀態,也就是說這時系統處於不可寫狀態,直到步驟5完成後才能恢復。在這個不可用狀態中,比較耗時的是步驟3,可能需要耗費好幾秒的時間。也是爲什麼需要在步驟1先做判斷,確保seconds_behind_master的值足夠小

系統的不可用時間是由這個數據可靠性優先的策略決定的

2、可用性優先策略

可用性優先策略:如果強行把可靠性優先策略的步驟4、5調整到最開始執行,也就是說不等主備數據同步,直接把連接切到備庫B,並且讓備庫B可以讀寫,那麼系統幾乎沒有不可用時間。這個切換流程的代價,就是可能出現數據不一致的情況

mysql> CREATE TABLE `t` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `c` int(11) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

insert into t(c) values(1),(2),(3);

表t定義了一個自增主鍵id,初始化數據後,主庫和備庫上都是3行數據。繼續在表t上執行兩條插入語句的命令,依次是:

insert into t(c) values(4);
insert into t(c) values(5);

假設,現在主庫上其他的數據表有大量的更新,導致主備延遲達到5秒。在插入一條c=4的語句後,發起了主備切換

下圖是可用性優先策略,且binlog_format=mixed時的切換流程和數據結果
在這裏插入圖片描述
1.步驟2中,主庫A執行完insert語句,插入了一行數據(4,4),之後開始進行主備切換

2.步驟3中,由於主備之間有5秒的延遲,所以備庫B還沒來得及應用插入c=4這個中轉日誌,就開始接收客戶端插入c=5的命令

3.步驟4中,備庫B插入了一行數據(4,5),並且把這個binlog發給主庫A

4.步驟5中,備庫B執行插入c=4這個中轉日誌,插入了一行數據(5,4)。而直接在備庫B執行的插入c=5這個語句,傳到主庫A,就插入了一行新數據(5,5)

最後的結果就是,主庫A和備庫B上出現了兩行不一致的數據

可用性優先策略,設置binlog_format=row
在這裏插入圖片描述
因此row格式在記錄binlog的時候,會記錄新插入的行的所有字段值,所以最後只會有一行不一致。而且,兩邊的主備同步的應用線程會報錯duplicate key error並停止。也就是說,這種情況下,備庫B的(5,4)和主庫A的(5,5)這兩行數據都不會被對方執行

3、小結

1.使用row格式的binlog時,數據不一致問題更容易被發現。而使用mixed或者statement格式的binlog時,可能過了很久才發現數據不一致的問題

2.主備切換的可用性優先策略會導致數據不一致。因此,大多數情況下,建議採用可靠性優先策略

五、MySQL的並行複製策略

在這裏插入圖片描述
主備的並行複製能力,要關注的就是上圖中黑色的兩個箭頭。一個代表客戶端寫入主庫,另一個代表備庫上sql_thread執行中轉日誌

在MySQL5.6版本之前,MySQL只支持單線程複製,由此在主庫併發高、TPS高時就會出現嚴重的主備延遲問題

多線程複製機制都是把只有一個線程的sql_thread拆成多個線程,都符合下面這個模型:
在這裏插入圖片描述
coordinator就是原來的sql_thread,不過現在它不再直接更新數據了,只負責讀取中轉日誌和分發事務。真正更新日誌的,變成了worker線程。而worker線程的個數就是由參數slave_parallel_workers決定的

coordinator在分發的時候,需要滿足以下兩個基本要求:

  • 不能造成更新覆蓋。這就要求更新同一行的兩個事務,必須被分發到同一個worker中
  • 同一個事務不能被拆開,必須放到同一個worker中

1、MySQL5.6版本的並行複製策略

MySQL5.6版本支持了並行複製,只是支持的粒度是按庫並行。用於決定分發策略的hash表裏,key是數據庫名

這個策略的並行效果取決於壓力模型。如果在主庫上有多個DB,並且各個DB的壓力均衡,使用這個策略的效果會很好

這個策略的兩個優勢:

  • 構造hash值的時候很快,只需要庫名
  • 不要求binlog的格式,因爲statement格式的binlog也可以很容易拿到庫名

可以創建不同的DB,把相同熱度的表均勻分到這些不同的DB中,強行使用這個策略

2、MariaDB的並行複製策略

redo log組提交優化,而MariaDB的並行複製策略利用的就是這個特性:

  • 能夠在同一個組裏提交的事務,一定不會修改同一行
  • 主庫上可以並行執行的事務,備庫上也一定是可以並行執行的

在實現上,MariaDB是這麼做的:

1.在一組裏面一起提交的事務,有一個相同的commit_id,下一組就是commit_id+1

2.commit_id直接寫到binlog裏面

3.傳到備庫應用的時候,相同commit_id的事務分發到多個worker執行

4.這一組全部執行完成後,coordinator再去取下一批

下圖中假設三組事務在主庫的執行情況,trx1、trx2和trx3提交的時候,trx4、trx5和trx6是在執行的。這樣,在第一組事務提交完成的時候,下一組事務很快就會進入commit狀態
在這裏插入圖片描述
按照MariaDB的並行複製策略,備庫上的執行效果如下圖:
在這裏插入圖片描述
在備庫上執行的時候,要等第一組事務完全執行完成後,第二組事務才能開始執行,這樣系統的吞吐量就不夠

另外,這個方案容易被大事務拖後腿。假設trx2是一個超大事務,那麼在備庫應用的時候,trx1和trx3執行完成後,下一組才能開始執行。只有一個worker線程在工作,是對資源的浪費

3、MySQL5.7版本的並行複製策略

MySQL5.7版本由參數slave-parallel-type來控制並行複製策略:

  • 配置爲DATABASE,表示使用MySQL5.6版本的按庫並行策略
  • 配置爲LOGICAL_CLOCK,表示的就是類似MariaDB的策略。MySQL在此基礎上做了優化

同時處於執行狀態的所有事務,是不是可以並行?

不可以,因爲這裏面可能有由於鎖衝突而處於鎖等待狀態的事務。如果這些事務在備庫上被分配到不同的worker,就會出現備庫跟主庫不一致的情況

而MariaDB這個策略的核心是所有處於commit狀態的事務可以並行。事務處於commit狀態表示已經通過了鎖衝突的檢驗了
在這裏插入圖片描述
其實只要能夠達到redo log prepare階段就表示事務已經通過鎖衝突的檢驗了

因此,MySQL5.7並行複製策略的思想是:

1.同時處於prepare狀態的事務,在備庫執行時是可以並行的

2.處於prepare狀態的事務,與處於commit狀態的事務之間,在備庫執行時也是可以並行的

binlog組提交的時候有兩個參數:

  • binlog_group_commit_sync_delay參數表示延遲多少微妙後才調用fsync
  • binlog_group_commit_sync_no_delay_count參數表示基類多少次以後才調用fsync

這兩個參數是用於故意拉長binlog從write到fsync的時間,以此減少binlog的寫盤次數。在MySQL5.7的並行複製策略裏,它們可以用來製造更多的同時處於prepare階段的事務。這樣就增加了備庫複製的並行度。也就是說,這兩個參數既可以故意讓主庫提交得慢些,又可以讓備庫執行得快些

4、MySQL5.7.22的並行複製策略

MySQL5.7.22增加了一個新的並行複製策略,基於WRITESET的並行複製,新增了一個參數binlog-transaction-dependency-tracking用來控制是否啓用這個新策略。這個參數的可選值有以下三種:

  • COMMIT_ORDER,根據同時進入prepare和commit來判斷是否可以並行的策略
  • WRITESET,表示的是對於事務涉及更新的每一行,計算出這一行的hash值,組成集合writeset。如果兩個事務沒有操作相同的行,也就是說它們的writeset沒有交集,就可以並行
  • WRITESET_SESSION,是在WRITESET的基礎上多了一個約束,即在主庫上同一個線程先後執行的兩個事務,在備庫執行的時候,要保證相同的先後順序

爲了唯一標識,hash值是通過庫名+表名+索引名+值計算出來的。如果一個表上除了有主鍵索引外,還有其他唯一索引,那麼對於每個唯一索引,insert語句對應的writeset就要多增加一個hash值

1.writeset是在主庫生成後直接寫入到binlog裏面的,這樣在備庫執行的時候不需要解析binlog內容

2.不需要把整個事務的binlog都掃一遍才能決定分發到哪個worker,更省內存

3.由於備庫的分發策略不依賴於binlog內容,索引binlog是statement格式也是可以的

對於表上沒主鍵和外鍵約束的場景,WRITESET策略也是沒法並行的,會暫時退化爲單線程模型

六、主庫出問題了,從庫怎麼辦?

下圖是一個基本的一主多從結構
在這裏插入圖片描述
圖中,虛線箭頭表示的是主備關係,也就是A和A’互爲主備,從庫B、C、D指向的是主庫A。一主多從的設置,一般用於讀寫分離,主庫負責所有的寫入和一部分讀,其他的讀請求則由從庫分擔
在這裏插入圖片描述
一主多從結構在切換完成後,A’會成爲新的主庫,從庫B、C、D也要改接到A’

1、基於位點的主備切換

當我們把節點B設置成節點A’的從庫的時候,需要執行一條change master命令:

CHANGE MASTER TO 
MASTER_HOST=$host_name 
MASTER_PORT=$port 
MASTER_USER=$user_name 
MASTER_PASSWORD=$password 
MASTER_LOG_FILE=$master_log_name 
MASTER_LOG_POS=$master_log_pos  
  • MASTER_HOST、MASTER_PORT、MASTER_USER和MASTER_PASSWORD四個參數,分別代表了主庫A’的IP、端口、用戶名和密碼
  • 最後兩個參數MASTER_LOG_FILE和MASTER_LOG_POS表示,要從主庫的master_log_name文件的master_log_pos這個位置的日誌繼續同步。而這個位置就是所說的同步位點,也就是主庫對應的文件名和日誌偏移量

找同步位點很難精確取到,只能取一個大概位置。一種去同步位點的方法是這樣的:

1.等待新主庫A’把中轉日誌全部同步完成

2.在A’上執行show master status命令,得到當前A’上最新的File和Position

3.取原主庫A故障的時刻T

4.用mysqlbinlog工具解析A’的File,得到T時刻的位點,這個值就可以作爲$master_log_pos

這個值並不精確,有這麼一種情況,假設在T這個時刻,主庫A已經執行完成了一個insert語句插入了一行數據R,並且已經將binlog傳給了A’和B,然後在傳完的瞬間主庫A的主機就掉電了。那麼,這時候系統的狀態是這樣的:

1.在從庫B上,由於同步了binlog,R這一行已經存在

2.在新主庫A’上,R這一行也已經存在,日誌是寫在master_log_pos這個位置之後的

3.在從庫B上執行change master命令,指向A’的File文件的master_log_pos位置,就會把插入R這一行數據的binlog又同步到從庫B去執行,造成主鍵衝突,然後停止tongue

通常情況下,切換任務的時候,要先主動跳過這些錯誤,有兩種常用的方法

一種是,主動跳過一個事務

set global sql_slave_skip_counter=1;
start slave;

另一種方式是,通過設置slave_skip_errors參數,直接設置跳過指定的錯誤。這個背景是,我們很清楚在主備切換過程中,直接跳過這些錯誤是無損的,所以纔可以設置slave_skip_errors參數。等到主備間的同步關係建立完成,並穩定執行一段時間之後,還需要把這個參數設置爲空,以免之後真的出現了主從數據不一致,也跳過了

2、GTID

MySQL5.6引入了GTID,是一個全局事務ID,是一個事務提交的時候生成的,是這個事務的唯一標識。它的格式是:

GTID=source_id:transaction_id
  • source_id是一個實例第一次啓動時自動生成的,是一個全局唯一的值
  • transaction_id是一個整數,初始值是1,每次提交事務的時候分配給這個事務,並加1

GTID模式的啓動只需要在啓動一個MySQL實例的時候,加上參數gtid_mode=on和enforce_gtid_consistency=on就可以了

在GTID模式下,每個事務都會跟一個GTID一一對應。這個GTID有兩種生成方式,而使用哪種方式取決於session變量gtid_next的值

1.如果gtid_next=automatic,代表使用默認值。這時,MySQL就把GTID分配給這個事務。記錄binlog的時候,先記錄一行SET@@SESSION.GTID_NEXT=‘GTID’。把這個GTID加入本實例的GTID集合

2.如果gtid_next是一個指定的GTID的值,比如通過set gtid_next=‘current_gtid’,那麼就有兩種可能:

  • 如果current_gtid已經存在於實例的GTID集合中,接下里執行的這個事務會直接被系統忽略
  • 如果current_gtid沒有存在於實例的GTID集合中,就將這個current_gtid分配給接下來要執行的事務,也就是說系統不需要給這個事務生成新的GTID,因此transaction_id也不需要加1

一個current_gtid只能給一個事務使用。這個事務提交後,如果要執行下一個事務,就要執行set命令,把gtid_next設置成另外一個gtid或者automatic

這樣每個MySQL實例都維護了一個GTID集合,用來對應這個實例執行過的所有事務

3、基於GTID的主備切換

在GTID模式下,備庫B要設置爲新主庫A’的從庫的語法如下:

CHANGE MASTER TO 
MASTER_HOST=$host_name 
MASTER_PORT=$port 
MASTER_USER=$user_name 
MASTER_PASSWORD=$password 
master_auto_position=1 

其中master_auto_position=1就表示這個主備關係使用的是GTID協議

實例A’的GTID集合記爲set_a,實例B的GTID集合記爲set_b。我們在實例B上執行start slave命令,取binlog的邏輯是這樣的:

1.實例B指定主庫A’,基於主備協議建立連接

2.實例B把set_b發給主庫A’

3.實例A’算出set_a與set_b的差集,也就是所有存在於set_a,但是不存在於set_b的GTID的集合,判斷A’本地是否包含了這個差集需要的所有binlog事務

  • 如果不包含,表示A’已經把實例B需要的binlog給刪掉了,直接返回錯誤
  • 如果確認全部包含,A’從自己的binlog文件裏面,找出第一個不在set_b的事務,發給B

4.之後從這個事務開始,往後讀文件,按順序取binlog發給B去執行

4、GTID和在線DDL

如果是由於索引缺失引起的性能問題,可以在線加索引來解決。但是,考慮到要避免新增索引對主庫性能造成的影響,可以先在備庫加索引,然後再切換,在雙M結構下,備庫執行的DDL語句也會傳給主庫,爲了避免傳回後對主庫造成影響,要通過set sql_log_bin=off關掉binlog,但是操作可能會導致數據和日誌不一致

兩個互爲主備關係的庫實例X和實例Y,且當前主庫是X,並且都打開了GTID模式。這時的主備切換流程可以變成下面這樣:

  • 在實例X上執行stop slave
  • 在實例Y上執行DDL語句。這裏不需要關閉binlog
  • 執行完成後,查出這個DDL語句對應的GTID,記爲source_id_of_Y:transaction_id
  • 到實例X上執行一下語句序列:
set GTID_NEXT="source_id_of_Y:transaction_id";
begin;
commit;
set gtid_next=automatic;
start slave;

這樣做的目的在於,既可以讓實例Y的更新有binlog記錄,同時也可以確保不會在實例X上執行這條更新

七、MySQL讀寫分離

讀寫分離的基本結構如下圖:
在這裏插入圖片描述
讀寫分離的主要目的就是分攤主庫的壓力。上圖中的結構是客戶端主動做負載均衡,這種模式下一般會把數據庫的連接信息放在客戶端的連接層。由客戶端來選擇後端數據庫進行查詢

還有一種架構就是在MySQL和客戶端之間有一箇中間代理層proxy,客戶端只連接proxy,由proxy根據請求類型和上下文決定請求的分發路由
在這裏插入圖片描述
1.客戶端直連方案,因此少了一層proxy轉發,所以查詢性能稍微好一點,並且整體架構簡單,排查問題更方便。但是這種方案,由於要了解後端部署細節,所以在出現主備切換、庫遷移等操作的時候,客戶端都會感知到,並且需要調整數據庫連接信息。一般採用這樣的架構,一定會伴隨一個負責管理後端的組件,比如Zookeeper,儘量讓業務端只專注於業務邏輯開發

2.帶proxy的架構,對客戶端比較友好。客戶端不需要關注後端細節,連接維護、後端信息維護等工作,都是由proxy完成的。但這樣的話,對後端維護團隊的要求會更高,而且proxy也需要有高可用架構

在從庫上會讀到系統的一個過期狀態的現象稱爲過期讀

1、強制走主庫方案

強制走主庫方案其實就是將查詢請求做分類。通常情況下,可以分爲這麼兩類:

1.對於必須要拿到最新結果的請求,強制將其發到主庫上

2.對於可以讀到舊數據的請求,纔將其發到從庫上

這個方案最大的問題在於,有時候可能會遇到所有查詢都不能是過期讀的需求,比如一些金融類的業務。這樣的話,就需要放棄讀寫分離,所有讀寫壓力都在主庫,等同於放棄了擴展性

2、Sleep方案

主庫更新後,讀從庫之前先sleep一下。具體的方案就是,類似於執行一條select sleep(1)命令。這個方案的假設是,大多數情況下主備延遲在1秒之內,做一個sleep可以很大概率拿到最新的數據

以買家發佈商品爲例,商品發佈後,用Ajax直接把客戶端輸入的內容作爲最新商品顯示在頁面上,而不是真正地去數據庫做查詢。這樣,賣家就可以通過這個顯示,來確認產品已經發布成功了。等到賣家再刷新頁面,去查看商品的時候,其實已經過了一段時間,也就達到了sleep的目的,進而也就解決了過期讀的問題

但這個方案並不精確:

1.如果這個查詢請求本來0.5秒就可以在從庫上拿到正確結果,也會等1秒

2.如果延遲超過1秒,還是會出現過期讀

3、判斷主備無延遲方案

show slave status結果裏的seconds_behind_master參數的值,可以用來衡量主備延遲時間的長短

1.第一種確保主備無延遲的方法是,每次從庫執行查詢請求前,先判斷seconds_behind_master是否已經等於0。如果還不等於0,那就必須等到這個參數變爲0才能執行查詢請求

show slave status結果的部分截圖如下:
在這裏插入圖片描述
2.第二種方法,對比位點確保主備無延遲:

  • Master_Log_File和Read_Master_Log_Pos表示的是讀到的主庫的最新位點
  • Relay_Master_Log_File和Exec_Master_Log_Pos表示的是備庫執行的最新位點

如果Master_Log_File和Read_Master_Log_Pos和Relay_Master_Log_File和Exec_Master_Log_Pos這兩組值完全相同,就表示接收到的日誌已經同步完成

3.第三種方法,對比GTID集合確保主備無延遲:

  • Auto_Position=1表示這堆主備關係使用了GTID協議
  • Retrieved_Gitid_Set是備庫收到的所有日誌的GTID集合
  • Executed_Gitid_Set是備庫所有已經執行完成的GTID集合

如果這兩個集合相同,也表示備庫接收到的日誌都已經同步完成

4.一個事務的binlog在主備庫之間的狀態:

1)主庫執行完成,寫入binlog,並反饋給客戶端

2)binlog被從主庫發送給備庫,備庫收到

3)在備庫執行binlog完成

上面判斷主備無延遲的邏輯是備庫收到的日誌都執行完成了。但是,從binlog在主備之間狀態的分析中,有一部分日誌,處於客戶端已經收到提交確認,而備庫還沒收到日誌的狀態
在這裏插入圖片描述
這時,主庫上執行完成了三個事務trx1、trx2和trx3,其中:

  • trx1和trx2已經傳到從庫,並且已經執行完成了
  • trx3在主庫執行完成,並且已經回覆給客戶端,但是還沒有傳到從庫中

如果這時候在從庫B上執行查詢請求,按照上面的邏輯,從庫認爲已經沒有同步延遲,但還是查不到trx3的

4、配合semi-sync

要解決上面的問題,就要引入半同步複製。semi-sync做了這樣的設計:

1.事務提交的時候,主庫把binlog發送給從庫

2.從庫收到binlog以後,發回給主庫一個ack,表示收到了

3.主庫收到這個ack以後,才能給客戶端返回事務完成的確認

如果啓用了semi-sync,就表示所有給客戶端發送過確認的事務,都確保了備庫已經收到了這個日誌

semi-sync+位點判斷的方案,只對一主一備的場景是成立的。在一主多從場景中,主庫只要等到一個從庫的ack,就開始給客戶端返回確認。這時,在從庫上執行查詢請求,就有兩種情況:

1.如果查詢是落在這個響應了ack的從庫上,是能夠確保讀到最新數據

2.但如果查詢落到其他從庫上,它們可能還沒有收到最新的日誌,就會產生過期讀的問題

判斷同步位點的方案還有另外一個潛在的問題,即:如果在業務更新的高峯期,主庫的位點或者GTID集合更新很快,那麼上面的兩個位點等值判斷就會一直不成立,很有可能出現從庫上遲遲無法響應查詢請求的情況
在這裏插入圖片描述
上圖從狀態1到狀態4,一直處於延遲一個事務的狀態。但是,其實客戶端是在發完trx1更新後發起的select語句,我們只需要確保trx1已經執行完成就可以執行select語句了。也就是說,如果在狀態3執行查詢請求,得到的就是預期結果了

semi-sync配合主備無延遲的方案,存在兩個問題:

1.一主多從的時候,在某些從庫執行查詢請求會存在過期讀的現象

2.在持續延遲的情況下,可能出現過度等待的問題

5、等主庫位點方案

select master_pos_wait(file, pos[, timeout]);

這條命令的邏輯如下:

1.它是在從庫執行的

2.參數file和pos指的是主庫上的文件名和位置

3.timeout可選,設置爲正整數N表示這個函數最多等待N秒

這個命令正常返回的結果是一個正整數M,表示從命令開始執行,到應用完file和pos表示的binlog位置,執行了多少事務

1.如果執行期間,備庫同步線程發生異常,則返回NULL

2.如果等待超過N秒,就返回-1

3.如果剛開始執行的時候,就發現已經執行過這個位置了,則返回0
在這裏插入圖片描述
對於上圖中先執行trx1,再執行一個查詢請求的邏輯,要保證能夠查到正確的數據,可以使用這個邏輯:

1.trx1事務更新完成後,馬上執行show master status得到當前主庫執行到的File和Position

2.選定一個從庫執行查詢語句

3.在從庫上執行select master_pos_wait(file, pos, 1)

4.如果返回值是>=0的正整數,則在這個從庫執行查詢語句

5.否則,到主庫執行查詢語句

流程如下:
在這裏插入圖片描述

6、GTID方案

 select wait_for_executed_gtid_set(gtid_set, 1);

這條命令的邏輯如下:

1.等待,直到這個庫執行的事務中包含傳入的gtid_set,返回0

2.超時返回1

等主庫位點方案中,執行完事務後,還要主動去主庫執行show master status。而MySQL5.7.6版本開始,允許在執行完更新類事務後,把這個事務的GTID返回給客戶端,這樣等GTID的方案可以減少一次查詢

等GTID的流程如下:

1.trx1事務更新完成後,從返回包直接獲取這個事務的GTID,記爲gtid1

2.選定一個從庫執行查詢語句

3.在從庫上執行 select wait_for_executed_gtid_set(gtid1, 1);

4.如果返回值是0,則在這個從庫執行查詢語句

5.否則,到主庫執行查詢語句
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章