某些函数在某些情况下不能很好地复制
除非启用基于行的复制,否则
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
模式时生成警告。以下限制仅适用于基于语句的复制,不适用于基于行的复制。 处理用户级锁的
GET_LOCK()
、RELEASE_LOCK()
、IS_FREE_LOCK()
和IS_USED_LOCK()
函数在复制时,副本并不知道源上的并发上下文。因此,这些函数不应用于插入源表,因为副本上的内容将有所不同。例如,不要发出类似于INSERT INTO mytable VALUES(GET_LOCK(...))
的语句。使用
MIXED
模式时,这些函数会使用基于行的复制自动复制,并在使用STATEMENT
模式时生成警告。
在基于语句的复制生效时,对于上述限制的解决方法,可以使用将有问题的函数结果保存在用户变量中,并在后面的语句中引用该变量的策略。例如,以下单行INSERT
由于引用了UUID()
函数而存在问题
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
模式时生成警告。(Bug #12092, Bug #30244)