某些函數在某些條件下無法良好複製
USER()
、CURRENT_USER()
(或CURRENT_USER
)、UUID()
、VERSION()
和LOAD_FILE()
函數在未經變更的情況下複製,因此除非啟用以列為基礎的複製,否則在複本上無法可靠運作。(請參閱第 19.2.1 節,「複製格式」。)當使用
MIXED
模式時,USER()
和CURRENT_USER()
會使用以列為基礎的複製自動複製,並在STATEMENT
模式中產生警告。(另請參閱第 19.5.1.8 節,「CURRENT_USER() 的複製」。) 這也適用於VERSION()
和RAND()
。對於
NOW()
,二進位日誌包含時間戳記。這表示 來源上呼叫此函數所傳回的值會複製到複本。為了避免在不同時區的 MySQL 伺服器之間複製時發生非預期的結果,請在來源和複本上都設定時區。如需詳細資訊,請參閱第 19.5.1.33 節,「複製與時區」。為了說明在不同時區的伺服器之間複製時的潛在問題,假設來源位於紐約,複本位於斯德哥爾摩,而且兩個伺服器都使用當地時間。進一步假設,在來源上,您建立一個資料表
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
,就可以安全地忽略它。當使用
MIXED
模式時,SYSDATE()
會自動使用基於列的複寫進行複寫,並且在STATEMENT
模式下會產生警告。另請參閱 第 19.5.1.33 節「複寫與時區」。
以下限制僅適用於基於陳述式的複寫,不適用於基於列的複寫。 處理使用者層級鎖定的
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)