本文最初與2016年發布在我的個人微信公眾號裡面,現略有修改,發布在這裡。

MySQL [1] 的Statement Based Replication (SBR) 是一個暗坑無數的功能,可能導致主備機數據不一致,以及其它問題,所以在TDSQL中我們使用RBR。這裡就列舉幾條SBR的坑。

在此之前,先說說SBR的優點。與Row based Replication (RBR) 相比, 它可以避免傳輸大量的binlog 日誌從而減小網路和存儲系統(binlog文件)的負載。另外,某些時候在備機上面重新執行SQL語句反而會比逐條執行RBR的binlog要快,一個極端的例子是,如果一個表沒有主鍵,然後一個delete/update語句需要刪除/更新大量的行,那麼使用RBR將是噩夢,因為備機處理每一條這樣的binlog都會導致全表掃描從而大大降低備機的性能(所以在TDSQL中我們默認強制創建含有主鍵的表)。但是使用SBR的話,一次執行即可更新/刪除全部行,這樣就快了很多。

下面正式開始批評SBR了。首先,由於SBR模式下,SQL語句到了備機需要被重新執行,與RBR相比,就增加了重新執行SQL語句的額外開銷,包括解析,優化和執行SQL語句。RBR 則直接調用mysql的存儲引擎介面(handler API) 來執行行的插入、刪除和更新,完全跳過了mysql的優化器的處理邏輯。SQL語句到了備機需要被重新執行,在多種情況下就會導致主備機數據不一致,比如一個SQL語句調用了用戶定義的函數,調用了返回隨機值的函數,在數據表中使用了自增列,以及使用了上下文數據(context data, 比如用一個表的行數作為某個插入欄位值,或者在update/delete語句中使用了limit子句)等等。

另一個一致性陷阱在於使用MyISAM等非事務存儲引擎。在完全使用innodb等事務存儲引擎的情況下,MySQL replication 是crash safe的,也就是說,無論任何時刻mysql server crash了,或者OS crash了,或者機器斷電了,那麼mysql server都可以恢複數據到crash之前的狀態,確保事務的持久性和一致性,確保所有之前提交的事務的改動都存在,因為innodb自身支持事務,可以恢復,並且在近年版本的mysql和mariadb中,innodb可以使用到binlog 數據來完成恢復。(儘管組提交時候並沒有刷盤commit日誌)但是一旦使用了非事務存儲引擎,那麼一個事務的完整性就不存在了,也不是crash safe的了。在master和slave上面都是這樣。雖然mysql的各個分支和版本在replication實現中做了很多努力來避免一些問題,並且給用戶強制了一大堆暗坑無數的DOs&DONTs, 但是無法完全解決這個問題。當發生了crash之後,只要一個事務讀取 並且/或者 寫入到了MyISAM表,那麼這個事務的改動可能部分存在,部分消失,並且binlog與數據表也可能會有各種不同,然後,用戶的數據就無法使用了。 第三,在使用innodb/xtradb並且隔離級別是read committed時候,如果設置binlog_format為statement,那麼mysql會拒絕後續的插入/更新/刪除操作,錯誤信息是:

ERROR 1665 (HY000): Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT and at least one table uses a storage engine limited to row-based logging. InnoDB is limited to row-logging when transaction isolation level is READ COMMITTED or READ UNCOMMITTED.

但是在設置這兩個變數的時候卻並沒有報錯,這樣如果你的應用程序不檢查insert語句執行是否成功的話,你都不知道你其實沒有成功插入數據。這個組合也是會導致slave在並發執行時候的數據一致性問題。所以,還是推薦大家使用RBR和INNODB(或者其他支持事務的存儲引擎)的搭配,我們一直用它 :)

1. 這裡的mysql是指廣義的mysql,包括oracle mysql, percona, mariadb等
推薦閱讀:
相关文章