摘要
分析主從同步出現的原因,MySQL實現主從同步的原理,思考實現原理的局限性和優點
背景
在實際應用中主從同步常用于實現備份、負載均衡和高可用
。數據冗余的目的是提高數據的安全性,避免因磁盤損壞導致數據丟失的問題。讀寫分離的目的是減輕單臺主機的通信壓力,提高系統的吞吐量。一般業務中的讀操作要遠遠大于寫操作,使用主從同步將讀操作的請求量分散到多個從主機,使每臺從主機的壓力都變小。
原理
從整體上看MySQL實現主從同步主要有三個步驟:
- 在主庫上把數據更改記錄到二進制日志(Binary Log)中(這些記錄被稱為二進制日志事件)。
- 備庫將主庫上的日志復制到自己的中繼日志(Relay Log)中。
- 備庫讀取中繼日志中的事件,將其重放到備庫數據之上。
原理分析
保存數據更改事件
在每次準備提交事務完成數據更新前,主庫將數據更新的事件記錄到二進制日志中。MySQL會按事務提交的順序而非每條語句的執行順序來記錄二進制日志
。在記錄二進制日志后,主庫會告訴存儲引擎可以提交事務了。
MySQL實現了兩種方式記錄數據的更新,分別是基于語句和基于行。基于語句就是記錄寫操作命令,基于行是記錄執行寫操作后受影響的行數據。這兩種方式都有利弊,實際情況推薦是兩種方式混合使用。
基于語句的方式
實現簡單,只需要按事務執行順序把對應的SQL語句記錄下來即可。但是對于一些特殊的SQL會無法同步到正確的數據,如SQL中用到了CURRENT_USER()
等與當前環境相關的函數,在從節點重放SQL時可能會得到不一樣的數據。
如果正在使用觸發器或者存儲過程,就不要使用基于語句的復制模式,除非能夠清楚地確定不會碰到復制問題。
基于行的方式
將實際數據記錄在二進制日志中。這種方式最大的好處是可以正確地復制每一行
。在一些場景下可能沒有基于語句高效。如更新所有用戶的狀態,基于語句的方式只需要記錄一條SQL語句,而基于行的方式則需要記錄所有更改的用戶數據。
兩種復制方式的比較
- 復制的準確性:
- 基于行的復制幾乎可以準確無誤地復制所有數據變更,因為它記錄了每一行數據的變化,不受SQL語句的影響。這在處理復雜的SQL語句、存儲過程、觸發器和用戶定義函數時尤其重要,因為這些可能在不同的環境中產生不同的結果。
- 基于語句的復制則可能在某些情況下無法準確復制,比如當復制環境與主庫環境不完全相同時,由于SQL語句在從庫上的執行可能產生不同的結果。
- 資源消耗:
- 基于行的復制通常會占用更多的日志空間和網絡帶寬,因為它記錄了更多的數據。這在數據變更頻繁的場景中尤其明顯。
- 基于語句的復制通常消耗較少的資源,因為它只記錄執行的SQL語句,這在數據變更較小或網絡帶寬受限的環境中可能是更好的選擇。
- 性能影響:
- 基于行的復制在處理大量數據變更時可能會對主庫的性能產生更大影響,因為它需要記錄更多的信息。
- 基于語句的復制在執行簡單的查詢和變更時,通常對性能的影響較小。
- 可追溯性和審計:
- 基于行的復制提供了更好的可追溯性,因為可以明確看到哪些行數據發生了變化。
- 基于語句的復制可能在追蹤具體數據變更時不夠直觀。
- 故障恢復:
- 基于行的復制在故障恢復時可能更容易,因為可以明確知道哪些數據需要被恢復。
- 基于語句的復制可能需要更復雜的故障恢復策略,尤其是當遇到無法正確執行的語句時。
在實際應用中,MySQL從5.1版本開始引入了混合復制模式
(Mixed-Based Replication, MBR)。在這種模式下,MySQL默認嘗試使用SBR(基于語句的復制 [Statement-Based Replication]),但在檢測到SBR可能失敗的情況下自動切換到RBR(基于行的復制 [Row-Based Replication])。這種方式試圖平衡資源消耗和復制的準確性,是許多場景下的推薦選擇。
主節點與從節點同步數據
首先,備庫會啟動一個工作線程,稱為I/O線程,I/O線程跟主庫建立一個普通的客戶端連接,然后在主庫上啟動一個特殊的二進制轉儲(binlog dump)線程(該線程沒有對應的SQL命令),這個二進制轉儲線程會讀取主庫上二進制日志中的事件。它不會對事件進行輪詢。如果該線程追趕上了主庫,它將進入睡眠狀態,直到主庫發送信號量通知其有新的事件產生時才會被喚醒,備庫I/O線程會將接收到的事件記錄到中繼日志中。
重放SQL
從節點使用單獨的SQL線程重放SQL,線程從中繼日志中讀取事件并在備庫執行,從而實現備庫數據的更新。當SQL線程追趕上I/O線程時,中繼日志通常已經在系統緩存中,所以中繼日志的開銷很低。
這里是MySQL會經常出現主從延時的關鍵。因為MySQL主節點是在并發的接收寫操作,從節點是單線程方式的恢復數據,當寫操作并發高或者有寫操作執行慢的時候,就會出現主從延時。
思考
技術的發展是由業務推動的。MySQL最開始是沒有主從同步的功能的,隨著互聯網的發展,對數據冗余的需要和MySQL高性能的需求越來越強,主從同步概念也就出現了。
基于語句的復制(也稱為邏輯復制)早在MySQL 3.23版本中就存在,而基于行的復制方式在5.1版本中才被加進來。
主從同步的核心是數據同步。首先想到的應該是同步數據,只同步數據時在某些場景下,同步的代價會比較大。為了實現同步的功能,基于語句的優勢就體現出來了。
一項技術從想法到落地是一步步演進的,在演進的過程中會迭代很多次。很多現在用的框架、中間件等都是迭代了很多版本,所以有了想法先能落地是比較重要的,不能紙上談兵。
MySQL為什么只用一個SQL線程重發SQL語句?只有一個線程重放SQL,在很大程度上總會有延時的。這里我覺得不應該關注在主從延時上。主從同步從理論上說就不可能做到實時,理想情況下也會有幾十毫秒的延時。重點應該關注主從同步帶來的作用,它實現了數據冗余,提高MySQL的服務能力。
MySQL在5.6版本之后支持了并行復制,使用多條SQL線程重放SQL語句。
現代服務器通常配備多核CPU和高帶寬網絡,單線程復制模型無法充分利用這些資源。高性能和低延時對于許多現代應用程序至關重要。并行復制能夠幫助數據庫架構更好地支撐實時數據分析、在線交易處理等高要求場景。
盡管單個SQL線程進行重放有以下幾點優勢:
- 數據一致性:
單個SQL線程確保所有事件按照它們在主庫上的發生順序執行,這對于依賴于順序執行的事務非常重要,以維持數據的一致性。 - 事務完整性:
單個SQL線程有助于保證事務的原子性和隔離性,確保從庫上的事務執行與主庫完全相同。 - 簡化故障恢復:
使用單個SQL線程使得故障恢復更加簡單,因為不需要處理多個并發SQL線程可能引入的復雜性。 - 資源管理:
單線程設計減少了資源爭用,如CPU、內存和磁盤I/O,從而降低了系統開銷。
但是軟件的發展要緊隨市場的腳步,否則就會被淘汰。