6.5 不同MySQL版本之間的同步兼容性
最早的二進制格式是在MySQL 3.23中開發出來的。在MySQL 4.0中改進了,MySQL 5.0又改進了。在配置同步時需要升級服務器的話,它們之間的因果關系在"6.6 Upgrading a Replication Setup"中描述了。
如果只關心同步,任何MySQL 4.1.x版本和MySQL 4.0.x是一樣的,因為它們都使用相同格式的二進制日志。所以,這些版本是互相兼容的,它們之間可以無縫地運行同步。一個例外的情況是,MySQL 4.0.0到4.0.2由於開發的較早,無法和後來的版本互相兼容,所以不要使用它們(它們是4.0版本的alpha系列。它們之間的兼容性在發布包的手冊中均有相關文檔)。
下表展示了不同版本的MySQL之間的master/slave同步兼容性。
Master Master Master 3.23.33 and up 4.0.3 and up or any 4.1.x 5.0.0 Slave 3.23.33 and up yes no no Slave 4.0.3 and up yes yes no Slave 5.0.0 yes yes yes
一個通常的規則是,我們建議使用最近的MySQL版本,因為同步兼容性一直在改善。我們也建議master和slave都使用同一個版本。
6.6 升級同步
如果升級服務器時涉及到配置同步,升級設置的步驟跟當前版本以及升級後的版本不同而異。
6.6.1 升級同步到 4.0 或 4.1
本節適用於從MySQL 3.23升級到4.0或者4.1的情況。4.0的服務器必須是4.0.3或者更高,"6.5 Replication Compatibility Between MySQL Versions"中提到了。
把master從MySQL 3.23升級到4.0或4.1時,首先要確認這個master的所有slave都已經是4.0或4.1了,否則的話,要先升級slave:挨個關閉,升級,重啟,重啟同步等。
通過以下步驟可以實現安全地升級,假定master要升級到3.23,而slave已經是4.0或4.1了。注意,master升級後,不要重啟使用任何舊的二進制日志的同步,因為它會干擾到4.0或4.1 slave的同步。
在master上執行 FLUSH TABLES WITH READ LOCK 語句,阻止所有的更新。
等到所有的slave都跟上了master的數據更新。在master上執行 SHOW MASTER STATUS 語句取得二進制日志以及偏移位置。然後,再slave用這些值執行 SELECT MASTER_POS_WAIT() 語句,它會阻止slave上的同步且返回它已經同步的偏移位置。然後在slave上執行 STOP SLAVE 語句。
關閉master,將它升級到MySQL 4.0或4.1。
重啟master,記下它的新的二進制文件名。可以在master上執行 SHOW MASTER STATUS 語句來取得這些信息。然後在每個slave上都執行如下語句:
MySQL> CHANGE MASTER TO MASTER_LOG_FILE='binary_log_name',
-> MASTER_LOG_POS=4;
MySQL> START SLAVE;
6.6.2 升級同步到 5.0
本節適用於從MySQL 3.23,4.0或4.1升級到5.0的情況。4.0的服務器必須是4.0.3或者更高,"6.5 Replication Compatibility Between MySQL Versions"中提到了。
首先,注意到MySQL 5.0還是alpha發布系列。它在各方面都比舊版本好(更容易升級一些同步中重要的會話變量,例如 sql_mode;詳情請看"C.1.3 Changes in release 5.0.0 (22 Dec 2003: Alpha")。不過,它還沒經過廣泛測試。由於是alpha版本,我們不建議用於任何生產環境(現在已經可以用於生產了,譯者注)。
把master從MySQL 3.23,4.0或4.1升級到5.0.0時,首先要確認這個master的所有slave都已經是5.0.0了,否則的話,要先升級slave:挨個關閉,升級,重啟,重啟同步等。5.0.0的slave可以讀取升級前寫入的執行語句的中繼日志。升級完後的slave創建的中繼日志就是5.0格式了。
當所有的slave都升級完了,關閉master,升級到5.0.0,然後重啟。5.0.0的master也可以讀取舊格式的二進制日志。slave能識別舊的格式並且合理處理它們。master上新建的二進制日志都是5.0.0格式的。slave也能識別這格式。
換言之,在升級到5.0.0時無需特殊的規定,除非在升級master到5.0.0之前slave必須使用舊版本的。注意,把5.0.0降級到舊版本中不能自動地做了:必須確保所有的5.0.0格式二進制日志和中繼日志都已經處理完了,然後才能把它們刪除完成降級。
6.7 同步特性及已知問題
以下列出了同步支持什麼,不支持什麼。附加的 InnoDB 特殊相關的信息以及同步請看"16.7.5 InnoDB and MySQL Replication"。
AUTO_INCREMENT, LAST_INSERT_ID(), 和 TIMESTAMP 的值都能被正常同步。
USER(), UUID(), 和 LOAD_FILE() 函數都完完全全地同步到slave,因此可能不大可靠。MySQL 4.1.1以前的版本中的 CONNECTION_ID() 函數也是如此。從MySQL 4.1.1及更高以後,新的 PASSWORD() 函數可以正常同步,當然了,slave必須是4.1.1或更高或者不同步它。如果有舊版本的slave必須要同步 PASSWORD() 函數,那麼master啟動時必須增加 --old-password 選項,這樣在master上就用舊的方法來實現 PASSWord() 了(注意,MySQL 4.1.0的 PASSWord() 函數實現跟其他的版本都不同,最好不要同步4.1.0)。
從MySQL 4.0.14開始同步 FOREIGN_KEY_CHECKS 變量。從5.0.0開始同步 sql_mode, UNIQUE_CHECKS,和 SQL_AUTO_IS_NULL 變量。 SQL_SELECT_LIMIT 和 table_type 變量目前還不能被同步。
現在討論使用不同字符集的MySQL服務器間同步的問題。
首先,在master和slave上必須總是使用同樣的全局字符集以及校驗字符集(--default-character-set, --default-collation 都是相關的全局變量)。否則,slave上可能會出現鍵重復(duplicate-key)的錯誤,因為用master的字符集認為該鍵可能是唯一的,但是用slave的字符集則未必然。
第二,如果master必須低於MySQL 4.1.3,則會話(session)的字符集必須和全局值一樣(也就是說,不能執行 SET NAMES, SET CHARACTER SET 等語句),因為這些對字符集的修改在slave不能識別。如果master是4.1.3或者更高,slave也是這樣的話,那麼會話字符集就可以隨便修改了(執行 NAMES, CHARACTER SET, COLLATION_CLIENT, COLLATION_SERVER 等),並且這些修改都會被記錄到二進制日志中,然後同步到slave上,它就知道怎麼做了。該會話還會阻止試圖修改這些全局變量的操作;就如前面所說,master和slave必須使用同樣的全局字符集。
如果在master上有和全局變量 collation_server 不一樣字符集的數據庫,那麼就要設計 CREATE TABLE 語句使得數據表不隱式地使用該數據庫的默認字符集,因為這目前還是一個bug(Bug #2326);一個變通的辦法是在 CREATE TABLE 語句中顯式地聲明數據表的字符集以及校驗字符集。
有時可能會把master上的事務表同步到slave後變成非事務表。例如,可以在slave上把master的 InnoDB 表當成 MyISAM 表。不過,slave在一個 BEGIN/COMMIT 區塊中停止的話就有問題了,因為slave會從 BEGIN 重新開始。這個問題已經放到TODO中,很快會被修復。
更新語句中如果用到了用戶自定義變量(例如變量 @var_name)的情況下,在MySQL 3.23和4.0不能被正確同步。在 4.1 這已經修復了。注意,從MySQL 5.0開始,用戶變量就不區分大小寫了。在做MySQL 5.0和舊版本間的同步需要考慮到這個問題。
從4.1.1以及更高版本中,slave可以用SSL方式連接到master。
在master上執行的 CREATE TABLE 語句如果包括了 DATA DIRECTORY或 INDEX DIRECTORY 子句,那麼它也會應用於slave上。如果slave上不存在對應的目錄或者沒有權限時便出現問題。從MySQL 4.0.15開始,有個 sql_mode 選項叫 NO_DIR_IN_CREATE。如果slave的SQL模式包含這個選項,那麼它在同步 CREATE TABLE 語句前會忽略前面提到的2個子句。結果就是 MyISAM 的數據和索引文件都只能放在該表的數據庫目錄下。
盡管沒聽說過發生過類似的情況,不過理論上確實存在這種可能性:如果一個查詢被設計為非確定方式的修改數據,那麼可能導致master和slave的數據不一致。那麼,就把決定的權力交給查詢優化器吧。(通常這不是一個好的做法,甚至超出了同步的范圍,詳情請看"1.8.7.3 Open Bugs and Design DeficiencIEs in MySQL")
在MySQL 4.1.1之前,FLUSH, ANALYZE TABLE, OPTIMIZE TABLE,和 REPAIR TABLE 語句沒有寫入到二進制日志中,因此也不會同步到slave上。這通常不會引發問題,因為它們並沒有修改數據。不過在特定情況下可能導致問題。如果同步 MySQL 數據庫下的權限表,在更新時不是用 GRANT 語句,那麼必須在slave上執行那麼必須在slave上執行 FLUSH PRIVILEGES 語句才能使之生效。同樣地,如果還有一個 MyISAM 表是 MERGE 表的一部分,那麼必須在slave上手工執行 FLUSH TABLES 語句。從MySQL 4.1.1開始,這些語句都寫入二進制日志了(除非指定選項 NO_WRITE_TO_BINLOG 或它的同名選項 LOCAL)。一些例外的情況是 FLUSH LOGS, FLUSH SLAVE, 和 FLUSH TABLES WITH READ LOCK (它們中的任何一個同步到slave的話都可能導致問題)。例子可見"14.5.4.2 FLUSH Syntax"。
MySQL只支持一個master多個slave的機制。以後我們會增加一個表決算法,如果當前master出現問題時能自動切換。同時也會引進一個"代理"進程來幫助將 SELECT 查詢發送到不同的slave上達到負載均衡。
當服務器關閉,重啟後,所有的 MEMORY (HEAP) 表都清空了。從MySQL 4.0.18開始,master用以下方式同步它們:一旦master開始使用一個 MEMORY 表,它會在用完這些表之後在二進制日志中寫入一個 DELETE FROM 語句告訴slave把它們刪除。詳情請看"15.3 The MEMORY (HEAP) Storage Engine"。
除非關閉slave(只是關閉slave線程),臨時表也會同步;並且在slave上已經記錄了一些還未被執行的需要用到臨時表的更新語句。關閉slave再重啟後更新所需的臨時表就不復存在了。為了避免這個問題,在有臨時表時就不要關閉slave。或者,使用以下步驟:
提交一個 STOP SLAVE 語句。
使用 SHOW STATUS 語句檢查變量 Slave_open_temp_tables 的值。
如果它的值是0,運行 MySQLadmin shutdown 命令關閉slave。
如果它的值不是0,用 START SLAVE 語句重啟slave線程。
如果還有這樣的好運氣就再次執行同樣的步驟吧。^_^
我們會盡快解決這個問題。
如果在一個循環master/slave同步關系中指定 --log-slave-updates 選項,那麼就可以安全地連接到各個服務器上。注意,很多語句可能在這種設置環境下不能正常工作,除非程序中已經特別注意避免這種更新時潛在的問題了,因為可能在不同服務器上不同的順序上發生更新問題。這意味著可以設定像下面的循環:
A -> B -> C -> A
服務器ID都已經編碼到二進制日志中了,因此服務器A知道那些自己創建的日志,從而不會去執行它們(除非在服務器A上啟動時增加 --replicate-same-server-id 選項,這個選項只在極少數情況下設置有意義)。因此,這就不會存在無限循環了。不過這個循環只有在更新表時沒有發生沖突才不會發生問題。換言之,如果在A和C中同時插入一條記錄,那麼可能在A中不可能插入數據,因為它的鍵可能跟C的鍵沖突了。同樣地,也不能在兩個服務器上更新同一條記錄,除非2次更新操作間有足夠的時間間隔。
如果在slave上執行一個SQL語句後產生錯誤,那麼slave的SQL線程就終止,然後它在錯誤日志中寫入一條信息。可以連接到slave上,解決問題(例如,不存在表)後,運行 START SLAVE 語句重啟它。
可以放心地關閉master(干淨地)之後再重啟它。如果slave到master的連接斷開了,它會立刻重連。如果失敗了,slave會定期重試(默認是每60秒重試一次,可通過 --master-connect-retry 選項來修改)。slave也會處理網絡斷開的情況。不過,slave會在 slave_net_timeout 秒之後如果還沒收到來自master的數據才會當作網絡斷開的情況來處理。如果斷開時間不長,可以減少 slave_net_timeout 的值。詳情請看"5.2.3 Server System Variables"。
也可以放心地關閉slave(干淨地),它會記錄停止的地方。不干淨地關閉slave可能產生問題,特別是系統關閉了但緩存還沒刷新到磁盤時。可以提供不間斷電源來提高系統容錯性。master的不干淨關閉可能導致表和二進制內容的不一致;如果是 InnoDB 表,使用 --innodb-safe-binlog 選項在master上就能避免這個問題。詳情請看"5.9.4 The Binary Log"。
由於 MyISAM 表的非事務本質,就可能發生一個語句只更新了部分表就返回錯誤代碼的情況。例如,一個多重插入語句中,有一條記錄違反了約束鍵規則,一個更新語句在更新了一些記錄後輩殺掉了。如果在master上發生這種情況了,那麼slave線程會推出,等待數據庫管理員決定要怎麼做,除非這個錯誤代碼是合法的並且這個語句的執行結果也是一樣的錯誤代碼。並沒有關於錯誤代碼是否合法的詳細描述,一些錯誤代碼可以用 --slave-skip-errors 選項屏蔽掉。這個選項從MySQL 3.23.47開始就可以用了。
如果把非事務表同步到事務表時在一個 BEGIN/COMMIT 段內更新數據表了,如果在非事務表提交之前有其他線程更新它了,那麼這個更新操作就不會正確地同步到二進制日志中。這是因為只有整個事務成功提交了才會寫到二進制日志中。
在4.0.15之前,任何在非事務表的更新操作會在它執行的時候立刻寫入到二進制日志中,然而事務表的更新只有在 COMMIT 後才寫入,ROLLBACK 的話就不寫入了。因此在一些事務中更新事務表或非事務表時就需要考慮這個情況了(不只是同步時會碰到這個問題,想要把二進制日志作為備份時也一樣)。在MySQL 4.0.15中,我們已經修改了更新事務和非事務表混合的情況下的日志記錄行為,它解決了這個問題(對於二進制日志來說順序地記錄語句是比較不錯的做法,所有必須的語句都會寫進去,ROLLBACK 也一樣)。當第二個連接更新非事務表而第一個連接的事務還沒結束時,就會有同樣的問題了;仍會發記錄語句順序發生錯誤的問題,因為第二個連接會在更新完成後立刻寫入到日志中。
當4.x的slave從3.23的master上同步 LOAD DATA INFILE 時,SHOW SLAVE STATUS 中的 Exec_Master_Log_Pos 和 Relay_Log_Space 字段的值就不正確了。Exec_Master_Log_Pos 值不正確的話在重啟slave之後會導致問題;因此最好在重啟前修改一下這個值,只需在master上運行 FLUSH LOGS。這個bug在MySQL 5.0.0的slave中已經解決了。
下表列出了MySQL 3.23同步時會發生的問題,它們在MySQL 4.0已經解決了:
LOAD DATA INFILE 能正確處理,只要那個數據文件在更新開始時仍然存在於master上。
LOAD DATA LOCAL INFILE 不再像以前3.23那樣被略過了。
在3.23中,RAND() 更新同步不正常。因此在使用 RAND() 更新時采用 RAND(some_non_rand_expr) 格式。例如,可以用 UNIX_TIMESTAMP() 作為 RAND() 的參數。