Documentation Home
MySQL 8.3 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 40.8Mb
PDF (A4) - 40.9Mb
Man Pages (TGZ) - 294.0Kb
Man Pages (Zip) - 409.0Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb
Excerpts from this Manual

MySQL 8.3 Reference Manual  /  ...  /  Replication and System Functions

19.5.1.14 复制和系统函数

某些函数在某些条件下不能很好地复制:

  • 函数 USER()CURRENT_USER()(或CURRENT_USER)、UUID()VERSION()LOAD_FILE() 在复制时不进行更改,因此除非启用基于行的复制,否则它们在副本上不可靠地工作。(见第 19.2.1 节,“复制格式”。)

    USER()CURRENT_USER() 在使用 MIXED 模式时自动使用基于行的复制,并在 STATEMENT 模式下生成警告。(见 also 第 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。因此,如果您在副本的 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.33 节,“复制和时区”

  • 以下限制仅适用于基于语句的复制,不适用于基于行的复制。 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 语句中可用。

同样的想法也适用于多行插入,但使用起来更加 cumbersom。对于双行插入,可以这样做:

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)