某些函數在某些情況下無法順利複寫
USER()
、CURRENT_USER()
(或CURRENT_USER
)、UUID()
、VERSION()
和LOAD_FILE()
函數在複寫時不會變更,因此除非啟用列基礎複寫,否則它們在複本上無法可靠運作。(請參閱第 19.2.1 節,「複寫格式」。)USER()
和CURRENT_USER()
在使用MIXED
模式時,會自動使用基於列的複製方式進行複製,並且在STATEMENT
模式下會產生警告。(另請參閱 章節 19.5.1.8,「CURRENT_USER() 的複製」。)VERSION()
和RAND()
也是如此。對於
NOW()
,二進制日誌會包含時間戳記。這表示在來源伺服器上呼叫此函式所回傳的值會被複製到副本伺服器。為了避免在不同時區的 MySQL 伺服器之間進行複製時產生非預期的結果,請在來源和副本伺服器上設定時區。更多資訊請參閱 章節 19.5.1.34,「複製與時區」。為了說明在不同時區的伺服器之間進行複製時可能出現的問題,假設來源伺服器位於紐約,副本伺服器位於斯德哥爾摩,且兩台伺服器都使用本地時間。進一步假設,在來源伺服器上,您建立一個名為
mytable
的資料表,對此資料表執行INSERT
陳述式,然後從資料表選取資料,如下所示:mysql> CREATE TABLE mytable (mycol TEXT); Query OK, 0 rows affected (0.06 sec) mysql> INSERT INTO mytable VALUES ( NOW() ); Query OK, 1 row affected (0.00 sec) mysql> SELECT * FROM mytable; +---------------------+ | mycol | +---------------------+ | 2009-09-01 12:00:00 | +---------------------+ 1 row in set (0.00 sec)
斯德哥爾摩的本地時間比紐約晚 6 小時;因此,如果您在完全相同的時間在副本伺服器上執行
SELECT NOW()
,則會回傳值2009-09-01 18:00:00
。因此,如果在剛才顯示的CREATE TABLE
和INSERT
陳述式被複製後,您從副本伺服器上的mytable
副本選取資料,您可能會預期mycol
會包含值2009-09-01 18:00:00
。但是,情況並非如此;當您從副本伺服器上的mytable
副本選取資料時,您會得到與來源伺服器上完全相同的結果。mysql> SELECT * FROM mytable; +---------------------+ | mycol | +---------------------+ | 2009-09-01 12:00:00 | +---------------------+ 1 row in set (0.00 sec)
與
NOW()
不同,SYSDATE()
函式並非複製安全,因為它不受二進制日誌中SET TIMESTAMP
陳述式的影響,並且如果使用基於陳述式的記錄方式,則不具決定性。如果使用基於列的記錄方式,則這不是問題。另一種替代方案是使用
--sysdate-is-now
選項,使SYSDATE()
成為NOW()
的別名。這必須在來源和副本伺服器上執行才能正確運作。在這種情況下,此函式仍會發出警告,但只要來源和副本伺服器都使用--sysdate-is-now
,就可以安全地忽略它。SYSDATE()
在使用MIXED
模式時,會自動使用基於列的複製方式進行複製,並且在STATEMENT
模式下會產生警告。另請參閱 章節 19.5.1.34,「複製與時區」。
以下限制僅適用於基於陳述式的複製,不適用於基於列的複製。處理使用者層級鎖定的
GET_LOCK()
、RELEASE_LOCK()
、IS_FREE_LOCK()
和IS_USED_LOCK()
函式在複製時,副本伺服器並不知道來源伺服器上的並行環境。因此,不應使用這些函式將資料插入來源資料表中,因為副本伺服器上的內容會有所不同。例如,請勿發出類似INSERT INTO mytable VALUES(GET_LOCK(...))
的陳述式。這些函式在使用
MIXED
模式時,會自動使用基於列的複製方式進行複製,並且在STATEMENT
模式下會產生警告。
當基於陳述式的複製生效時,為了規避上述限制,您可以使用將有問題的函式結果儲存在使用者變數中,然後在後續的陳述式中參考該變數的策略。例如,由於參考了 UUID()
函式,以下單列 INSERT
會產生問題:
INSERT INTO t VALUES(UUID());
為了解決此問題,請改為執行此操作:
SET @my_uuid = UUID();
INSERT INTO t VALUES(@my_uuid);
這一系列的陳述式會被複製,因為 @my_uuid
的值會以使用者變數事件的形式儲存在二進制日誌中,然後才會執行 INSERT
陳述式,因此 INSERT
可以使用該值。
相同的概念也適用於多列插入,但使用起來更加繁瑣。對於雙列插入,您可以執行此操作:
SET @my_uuid1 = UUID(); @my_uuid2 = UUID();
INSERT INTO t VALUES(@my_uuid1),(@my_uuid2);
但是,如果列數很大或未知,則此解決方案難以實現或不可行。例如,您無法將以下陳述式轉換為將個別使用者變數與每一列關聯的陳述式:
INSERT INTO t2 SELECT UUID(), * FROM t1;
在儲存函式中,只要在函式執行期間僅呼叫一次 RAND()
,它就可以正確地複製。(您可以將函式執行時間戳記和隨機數種子視為在來源和副本伺服器上相同的隱含輸入。)
FOUND_ROWS()
和 ROW_COUNT()
函式在使用基於陳述式的複製時無法可靠地複製。一個解決方法是將函式呼叫的結果儲存在使用者變數中,然後在 INSERT
陳述式中使用該變數。例如,如果您希望將結果儲存在名為 mytable
的資料表中,您通常會像這樣做:
SELECT SQL_CALC_FOUND_ROWS FROM mytable LIMIT 1;
INSERT INTO mytable VALUES( FOUND_ROWS() );
但是,如果您要複製 mytable
,則應該使用 SELECT ... INTO
,然後將變數儲存在資料表中,如下所示:
SELECT SQL_CALC_FOUND_ROWS INTO @found_rows FROM mytable LIMIT 1;
INSERT INTO mytable VALUES(@found_rows);
這樣,使用者變數會作為上下文的一部分被複製,並且在副本伺服器上正確應用。
這些函式在使用 MIXED
模式時,會自動使用基於列的複製方式進行複製,並且在 STATEMENT
模式下會產生警告。(錯誤 #12092、錯誤 #30244)