玩過MySQL的人應該都知道,由于MySQL是邏輯複制,從根子上是難以保證資料一緻性的。玩MySQL玩得好的專家們知道有哪些坑,應該怎麼回避。為了保障MySQL資料的一緻性,甚至會動用paxos,raft之類的終極武器建立嚴密的防護網。如果不會折騰,真不建議用MySQL存放一緻性要求高的資料。
PostgreSQL由于是實體複制,天生就很容易保障資料一緻性,而且回放日志的效率很高。 我們實測的結果,MySQL5.6的寫qps超過4000備機就跟不上主機了;PG 8核虛機的寫qps壓到2.3w備機依然毫無壓力,之是以隻壓到2.3w是因為主節點的CPU已經跑滿壓不上去了。
那麼相比于MySQL,PG有哪些措施用于保障資料的一緻性呢?
PG的備庫處于恢複狀态,不斷的回放主庫的WAL,不具備寫能力。
而MySQL的單寫是通過在備機上設定read_only或super_read_only實作的,DBA在維護資料庫的時候可能需要解除隻讀狀态,在解除期間發生點什麼,或自動化腳本出個BUG都可能引起主備資料不一緻。甚至備庫在和主庫建立複制關系之前資料就不是一緻的,MySQL的邏輯複制并不阻止兩個不一緻的庫建立複制關系。
PG的備庫以和主庫完全相同順序串行化的回放WAL日志。
MySQL中由于存在組送出,以及為了解決單線程複制回放慢而采取的并行複制,不得不在複制延遲和資料一緻性之前做取舍。 并且這裡牽扯到的邏輯很複雜,已經檢出了很多的BUG;因為邏輯太複雜了,未來出現新BUG的機率應該相對也不會低。
PG通過synchronous_commit參數設定複制的持久性級别。
下面這些級别越往下越嚴格,從remote_write開始就可以保證單機故障不丢資料了。
off
local
remote_write
on
remote_apply
MySQL通過半同步複制在很大程度上降低了failover丢失資料的機率。MySQL的主庫在等待備庫的應答逾時時半同步複制會自動降級成異步,此時發生failover會丢失資料。
WAL檔案頭中儲存了資料庫執行個體的唯一辨別(Database system identifier),可以確定不同資料庫執行個體産生的WAL可以差別開,同一叢集的主備庫擁有相同唯一辨別。
PG提升備機的時候會同時提升備機的時間線,時間線是WAL檔案名的一部分,通過時間線就可以把新主和舊主産生的WAL差別開。 (如果同時提升2個以上的備機,就無法這樣區分WAL了,當然這種情況正常不應該發生。)
WAL記錄在整個WAL邏輯資料流中的偏移(lsn)作為WAL的辨別。
以上3者的聯合可唯一辨別WAL記錄
MySQL5.6開始支援GTID了,這對保障資料一緻性是個極大的進步。對于邏輯複制來說,GITD确實做得很棒,但是和PG實體複制的時間線+lsn相比起來就顯得太複雜了。時間線+lsn隻是2個數字而已;GTID卻是一個複雜的集合,而且需要定期清理。
MySQL的GTID是長這樣的:
在初始化資料庫時,使用-k選項可以打開資料檔案的checksum功能。(建議打開,造成的性能損失很小) 如果底層存儲出現問題,可通過checksum及時發現。
MySQL也隻支援資料檔案的checksum,沒什麼差別。
每條WAL記錄裡都儲存了checksum資訊,如果WAL的傳輸存儲過程中出現錯誤可及時發現。
MySQL的binlog記錄裡也包含checksum,沒什麼差別。
WAL可能來自歸檔的拷貝或人為拷貝,PG在讀取WAL檔案時會進行驗證,可防止DBA弄錯檔案。
檢查WAL檔案頭中記錄的資料庫執行個體的唯一辨別是否和本資料庫一緻
檢查WAL頁面頭中記錄的頁位址是否正确
其它檢查
上面第2項檢查的作用主要是應付WAL再利用。
PG在清理不需要的WAL檔案時,有2種方式,1是删除,2是改名為未來的WAL檔案名防止頻繁建立檔案。
看下面的例子,000000030000000000000015及以後的WAL檔案的修改日期比前面的WAL還要老,這些WAL檔案就是被重命名了的。
由于有上面的第2項檢查,如果讀到了這些WAL檔案,可以立即識别出來。
MySQL的binlog檔案名一般是長下面這樣的,從binlog檔案名上看不出任何和GTID的映射關系。
不同機器上産生的binlog檔案可能同名,如果要管理多套MySQL,千萬别拿錯檔案。因為MySQL是邏輯複制,這些binlog檔案就像SQL語句一樣,拿到哪裡都可以執行。