## 25.7 存储程序二进制日志

二进制日志包含有关修改数据库内容的 SQL 语句的信息。此信息以描述修改的“事件”的形式存储。（二进制日志事件与预定事件存储对象不同。）二进制日志有两个重要用途：

-   对于复制，在源复制服务器上使用二进制日志作为要发送到副本服务器的语句的记录。源将其二进制日志中包含的事件发送到其副本，副本执行这些事件以进行与源上相同的数据更改。看[第 17.2 节，“复制实现”](replication-implementation.html).

-   某些数据恢复操作需要使用二进制日志。恢复备份文件后，将重新执行备份后记录的二进制日志中的事件。这些事件使数据库从备份点开始更新。看[第 7.3.2 节，“使用备份进行恢复”](recovery-from-backups.html).

    但是，如果日志记录发生在语句级别，则存在与存储程序（存储过程和函数、触发器和事件）相关的某些二进制日志记录问题：

-   在某些情况下，一条语句可能会影响源和副本上的不同行集。

-   在副本上执行的复制语句由副本的应用程序线程处理。除非你实现了 MySQL 8.0.18 提供的复制权限检查（参见[第 17.3.3 节，“复制权限检查”](replication-privilege-checks.html))，应用程序线程具有完全权限。在这种情况下，过程可能会在源服务器和副本服务器上遵循不同的执行路径，因此用户可以编写包含仅在副本上执行的危险语句的例程。

-   如果修改数据的存储程序是不确定的，则它是不可重复的。这可能导致源和副本上的数据不同，或者导致恢复的数据与原始数据不同。

    本节描述 MySQL 如何处理存储程序的二进制日志记录。它说明了实现对存储程序的使用的当前条件，以及您可以采取哪些措施来避免日志记录问题。它还提供了有关这些情况的原因的其他信息。

    除非另有说明，否则此处的注释假定在服务器上启用了二进制日志记录（请参阅[第 5.4.4 节，“二进制日志”](binary-log.html).) 如果未启用二进制日志，则无法进行复制，也无法使用二进制日志进行数据恢复。从 MySQL 8.0 开始，二进制日志记录默认启用，并且仅在您指定[`--skip-log-bin`](replication-options-binary-log.html#option_mysqld_log-bin)要么[`--disable-log-bin`](replication-options-binary-log.html#option_mysqld_log-bin)启动时的选项。

    通常，当二进制日志记录发生在 SQL 语句级别（基于语句的二进制日志记录）时，会导致此处描述的问题。如果您使用基于行的二进制日志记录，则日志包含由于执行 SQL 语句而对单个行所做的更改。当例程或触发器执行时，记录行更改，而不是进行更改的语句。对于存储过程，这意味着[`称呼`](call.html)语句未记录。对于存储的函数，记录函数内所做的行更改，而不是函数调用。对于触发器，记录触发器所做的行更改。在副本方面，只看到行更改，而不是存储的程序调用。

    混合格式二进制日志（[`binlog_format=混合`](replication-options-binary-log.html#sysvar_binlog_format)) 使用基于语句的二进制日志，除非只有基于行的二进制日志才能保证产生正确的结果。对于混合格式，当存储函数、存储过程、触发器、事件或预准备语句包含任何对基于语句的二进制日志记录不安全的内容时，整个语句将被标记为不安全并以行格式记录。用于创建和删除过程、函数、触发器和事件的语句始终是安全的，并以语句格式记录。有关基于行、混合和基于语句的日志记录以及如何确定安全和不安全语句的更多信息，请参阅[第 17.2.1 节，“复制格式”](replication-formats.html).

    MySQL中使用存储函数的条件可以总结如下。这些条件不适用于存储过程或事件调度程序事件，除非启用二进制日志记录，否则它们不适用。

-   要创建或更改存储功能，您必须拥有[`SET_USER_ID`](privileges-provided.html#priv_set-user-id)特权（或已弃用的[`极好的`](privileges-provided.html#priv_super)特权），除了[`创建例程`](privileges-provided.html#priv_create-routine)要么[`改变常规`](privileges-provided.html#priv_alter-routine)通常需要的特权。（取决于`定义者`函数定义中的值，[`SET_USER_ID`](privileges-provided.html#priv_set-user-id)要么[`极好的`](privileges-provided.html#priv_super)无论是否启用二进制日志记录，都可能需要。看[第 13.1.17 节，“CREATE PROCEDURE 和 CREATE FUNCTION 语句”](create-procedure.html).)

-   创建存储函数时，必须声明它是确定性的或不修改数据。否则，数据恢复或复制可能不安全。

    默认情况下，对于[`创建函数`](create-function.html)声明被接受，至少其中一项`确定性`,`没有 SQL`， 要么`读取 SQL 数据`必须明确指定。否则会发生错误：

    ```
    ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL,
    or READS SQL DATA in its declaration and binary logging is enabled
    (you *might* want to use the less safe log_bin_trust_function_creators
    variable)
    ```

    这个函数是确定性的（并且不会修改数据），所以它是安全的：

    ```
    CREATE FUNCTION f1(i INT)
    RETURNS INT
    DETERMINISTIC
    READS SQL DATA
    BEGIN
      RETURN i;
    END;
    ```

    该功能使用[`UUID()`](miscellaneous-functions.html#function_uuid)，这不是确定性的，因此该函数也不是确定性的并且不安全：

    ```
    CREATE FUNCTION f2()
    RETURNS CHAR(36) CHARACTER SET utf8
    BEGIN
      RETURN UUID();
    END;
    ```

    此函数修改数据，因此可能不安全：

    ```
    CREATE FUNCTION f3(p_id INT)
    RETURNS INT
    BEGIN
      UPDATE t SET modtime = NOW() WHERE id = p_id;
      RETURN ROW_COUNT();
    END;
    ```

    对功能性质的评估基于创建者的“诚实”。MySQL 不检查声明的函数`确定性`没有产生不确定结果的陈述。

-   当您尝试执行存储的函数时，如果[`binlog_format=声明`](replication-options-binary-log.html#sysvar_binlog_format)已设置，则`确定性`必须在函数定义中指定关键字。如果不是这种情况，则会生成错误并且函数不会运行，除非[`log_bin_trust_function_creators=1`](replication-options-binary-log.html#sysvar_log_bin_trust_function_creators)指定覆盖此检查（见下文）。对于递归函数调用，`确定性`关键字仅在最外层调用中是必需的。如果正在使用基于行的或混合的二进制日志记录，则即使在定义函数时未使用`确定性`关键词。

-   因为 MySQL 在创建时不检查函数是否真的是确定性的，所以使用存储函数调用`确定性`关键字可能执行对基于语句的日志记录不安全的操作，或调用包含不安全语句的函数或过程。如果发生这种情况时[`binlog_format=声明`](replication-options-binary-log.html#sysvar_binlog_format)设置，发出警告消息。如果正在使用基于行或混合二进制日志记录，则不会发出警告，并且以基于行的格式复制语句。

-   放宽前面的函数创建条件（你必须有[`极好的`](privileges-provided.html#priv_super)特权，并且函数必须声明为确定性或不修改数据），设置全局[`日志\u bin\u信任\u函数\u创建者`](replication-options-binary-log.html#sysvar_log_bin_trust_function_creators)系统变量为1。默认情况下，此变量的值为0，但您可以这样更改：

    ```
    mysql> SET GLOBAL log_bin_trust_function_creators = 1;
    ```

    也可以在服务器启动时设置此变量。

    如果未启用二进制日志记录，[`日志\u bin\u信任\u函数\u创建者`](replication-options-binary-log.html#sysvar_log_bin_trust_function_creators)不适用。[`超级的`](privileges-provided.html#priv_super)函数创建不需要，除非如前所述`定义者`函数定义中的值需要它。

-   有关可能对复制不安全的内置函数（从而导致使用它们的存储函数也不安全）的信息，请参阅[第17.5.1节，“复制功能和问题”](replication-features.html).

    触发器与存储函数类似，因此前面关于函数的说明也适用于触发器，但以下情况除外：[`创建触发器`](create-trigger.html)没有可选选项`确定性`特征，所以触发器被认为总是确定性的。然而，这种假设在某些情况下可能是无效的。例如[`UUID（）`](miscellaneous-functions.html#function_uuid)函数是不确定的（并且不会复制）。在触发器中使用此类函数时要小心。

    触发器可以更新表，因此与存储函数类似的错误消息会在[`创建触发器`](create-trigger.html)如果您没有所需的特权。在副本端，副本使用触发器`定义者`属性来确定哪个用户被认为是触发器的创建者。

    本节的其余部分提供了有关日志记录实现及其含义的更多详细信息。除非您对当前存储的例行使用日志相关条件的基本原理感兴趣，否则无需阅读。本讨论仅适用于基于语句的日志记录，不适用于基于行的日志记录，但第一项除外：`创造`和`滴`无论记录模式如何，语句都会记录为语句。

-   服务器写入[`创建事件`](create-event.html), [`创建过程`](create-procedure.html), [`创建函数`](create-function.html), [`改变事件`](alter-event.html), [`改变程序`](alter-procedure.html), [`改变功能`](alter-function.html), [`丢弃事件`](drop-event.html), [`丢弃程序`](drop-procedure.html)和[`下降函数`](drop-function.html)将语句添加到二进制日志中。

-   存储函数调用记录为[`选择`](select.html)语句，如果函数更改了数据，并且发生在一个否则不会被记录的语句中。这可以防止由于在非记录语句中使用存储函数而导致的数据更改不被复制。例如[`选择`](select.html)语句不会写入二进制日志，而是[`选择`](select.html)可能会调用进行更改的存储函数。为了解决这个问题，一个`挑选*`func\_名称`*()`当给定函数进行更改时，语句会写入二进制日志。假设在源服务器上执行以下语句：

    ```
    CREATE FUNCTION f1(a INT) RETURNS INT
    BEGIN
      IF (a < 3) THEN
        INSERT INTO t2 VALUES (a);
      END IF;
      RETURN 0;
    END;

    CREATE TABLE t1 (a INT);
    INSERT INTO t1 VALUES (1),(2),(3);

    SELECT f1(a) FROM t1;
    ```

    当[`选择`](select.html)语句执行时，函数`f1（）`被调用三次。其中两个调用插入一行，MySQL记录一行[`选择`](select.html)他们每个人的声明。也就是说，MySQL将以下语句写入二进制日志：

    ```
    SELECT f1(1);
    SELECT f1(2);
    ```

    服务器还记录[`选择`](select.html)当函数调用导致错误的存储过程时，存储函数调用的语句。在这种情况下，服务器写入[`选择`](select.html)语句以及预期的错误代码。在复制副本上，如果发生相同的错误，则这是预期的结果，复制将继续。否则，复制将停止。

-   记录存储函数调用而不是函数执行的语句对复制有安全影响，这是由两个因素引起的：

    -   函数可以在源服务器和副本服务器上遵循不同的执行路径。

    -   在副本上执行的语句由副本的applier线程处理。除非您实现复制权限检查，这可以从MySQL 8.0.18获得（请参阅[第17.3.3节，“复制权限检查”](replication-privilege-checks.html))，应用程序线程具有完全权限。

        这意味着，尽管用户必须拥有[`创建例行程序`](privileges-provided.html#priv_create-routine)要创建函数，用户可以编写包含危险语句的函数，该语句只在副本上执行，副本由具有完全权限的线程处理。例如，如果源服务器和副本服务器的服务器ID值分别为1和2，则源服务器上的用户可能会创建并调用不安全的函数`不安全的_func（）`详情如下：

    ```
    mysql> delimiter //
    mysql> CREATE FUNCTION unsafe_func () RETURNS INT
        -> BEGIN
        ->   IF @@server_id=2 THEN dangerous_statement; END IF;
        ->   RETURN 1;
        -> END;
        -> //
    mysql> delimiter ;
    mysql> INSERT INTO t VALUES(unsafe_func());
    ```

    这个[`创建函数`](create-function.html)和[`插入`](insert.html)语句被写入二进制日志，因此复制副本执行它们。因为复制副本的applier线程具有完全权限，所以它会执行危险的语句。因此，函数调用对源和副本有不同的影响，并且不是复制安全的。

    为防止启用二进制日志记录的服务器出现这种危险，存储函数创建者必须具有[`超级的`](privileges-provided.html#priv_super)特权，除了通常的[`创建例行程序`](privileges-provided.html#priv_create-routine)所需的特权。同样地，使用[`改变功能`](alter-function.html)，你必须有[`超级的`](privileges-provided.html#priv_super)除了[`改变常规`](privileges-provided.html#priv_alter-routine)特权没有[`超级的`](privileges-provided.html#priv_super)特权时，会发生错误：

    ```
    ERROR 1419 (HY000): You do not have the SUPER privilege and
    binary logging is enabled (you *might* want to use the less safe
    log_bin_trust_function_creators variable)
    ```

    如果您不想要求函数创建者拥有[`超级的`](privileges-provided.html#priv_super)特权（例如，如果[`创建例行程序`](privileges-provided.html#priv_create-routine)在您的系统上拥有特权（有经验的应用程序开发人员），设置全局[`日志\u bin\u信任\u函数\u创建者`](replication-options-binary-log.html#sysvar_log_bin_trust_function_creators)系统变量为1。也可以在服务器启动时设置此变量。如果未启用二进制日志记录，[`日志\u bin\u信任\u函数\u创建者`](replication-options-binary-log.html#sysvar_log_bin_trust_function_creators)不适用。[`超级的`](privileges-provided.html#priv_super)函数创建不需要，除非如前所述`定义者`函数定义中的值需要它。

-   无论您如何选择函数创建者的权限，都建议在可用的情况下使用复制权限检查（来自MySQL 8.0.18）。可以设置复制权限检查，以确保仅为复制通道授权预期的和相关的操作。有关执行此操作的说明，请参阅[第17.3.3节，“复制权限检查”](replication-privilege-checks.html).

-   如果执行更新的函数是不确定的，则它是不可重复的。这可能会产生两种不良影响：

    -   这会导致副本与源不同。

    -   还原的数据与原始数据不匹配。

        为了解决这些问题，MySQL强制执行以下要求：在源服务器上，函数的创建和更改被拒绝，除非您声明函数是确定性的或不修改数据。这里有两组功能特征：

    -   这个`确定性`和`不确定`特征指示函数是否总是为给定的输入生成相同的结果。默认值是`不确定`如果两个特征都没有给出。要声明函数是确定性的，必须指定`确定性`明确地

    -   这个`包含SQL`, `没有SQL`, `读取SQL数据`和`修改SQL数据`特征提供有关函数是读取还是写入数据的信息。任何一个`没有SQL`或`读取SQL数据`指示函数不更改数据，但必须显式指定其中一个，因为默认值为`包含SQL`如果没有给出特征。

        默认情况下[`创建函数`](create-function.html)待接受的声明，至少一个`确定性`, `没有SQL`或`读取SQL数据`必须明确指定。否则会发生错误：

    ```
    ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL,
    or READS SQL DATA in its declaration and binary logging is enabled
    (you *might* want to use the less safe log_bin_trust_function_creators
    variable)
    ```

    如果你设定[`日志\u bin\u信任\u函数\u创建者`](replication-options-binary-log.html#sysvar_log_bin_trust_function_creators)对于1，函数是确定性的或不修改数据的要求被放弃。

-   存储过程调用记录在语句级别，而不是[`呼叫`](call.html)数量也就是说，服务器不记录[`呼叫`](call.html)语句，它将这些语句记录在实际执行的过程中。因此，源服务器上发生的相同更改也会发生在副本上。这可以防止程序在不同的机器上具有不同的执行路径可能导致的问题。

    通常，在存储过程中执行的语句会使用与以独立方式执行的语句相同的规则写入二进制日志。记录过程语句时需要特别注意，因为过程中的语句执行与非过程上下文中的语句执行不完全相同：

    -   [](<>)要记录的语句可能包含对本地过程变量的引用。这些变量不存在于存储过程上下文之外，因此不能直接记录引用此类变量的语句。相反，出于日志记录的目的，对局部变量的每个引用都将替换为此构造：

        ```
        NAME_CONST(var_name, var_value)
        ```

        *`var_名称`*是局部变量名，并且*`var_值`*是一个常量，指示记录语句时变量的值。[`NAME_CONST（）`](miscellaneous-functions.html#function_name-const)价值*`var_值`*，以及*`var_名称`*.因此，如果直接调用此函数，会得到如下结果：

        ```
        mysql> SELECT NAME_CONST('myname', 14);
        +--------+
        | myname |
        +--------+
        |     14 |
        +--------+
        ```

        [`NAME_CONST（）`](miscellaneous-functions.html#function_name-const)允许在副本上执行记录的独立语句，其效果与在存储过程中的源上执行的原始语句相同。

        使用[`NAME_CONST（）`](miscellaneous-functions.html#function_name-const)可能会给你带来麻烦[`创建表格。。。选择`](create-table.html)源列表达式引用局部变量时的语句。将这些引用转换为[`NAME_CONST（）`](miscellaneous-functions.html#function_name-const)表达式可能导致源服务器和副本服务器上的列名不同，或者列名太长，无法作为合法的列标识符。解决方法是为引用局部变量的列提供别名。当考虑这个陈述时`米瓦尔`值为1：

        ```
        CREATE TABLE t1 SELECT myvar;
        ```

        这一点改写如下：

        ```
        CREATE TABLE t1 SELECT NAME_CONST(myvar, 1);
        ```

        要确保源表和副本表具有相同的列名，请编写如下语句：

        ```
        CREATE TABLE t1 SELECT myvar AS myvar;
        ```

        重写的语句变成：

        ```
        CREATE TABLE t1 SELECT NAME_CONST(myvar, 1) AS myvar;
        ```

    -   要记录的语句可能包含对用户定义变量的引用。为了解决这个问题，MySQL编写了一个[`设置`](set-variable.html)语句，以确保复制副本上存在的变量与源上的变量具有相同的值。例如，如果一条语句引用一个变量`@我的`，该语句在二进制日志中的前面是以下语句，其中*`价值`*它的价值是什么`@我的`关于来源：

        ```
        SET @my_var = value;
        ```

    -   过程调用可以发生在提交或回滚的事务中。考虑事务上下文，以便正确复制过程执行的事务方面。也就是说，服务器记录过程中实际执行和修改数据的语句，并记录[`开始`](commit.html), [`犯罪`](commit.html)和[`回降`](commit.html)必要时发表声明。例如，如果一个过程只更新事务表，并且在回滚的事务中执行，那么这些更新不会被记录。如果该过程发生在已提交的事务中，[`开始`](commit.html)和[`犯罪`](commit.html)语句与更新一起记录。对于在回滚事务中执行的过程，其语句的记录规则与以独立方式执行语句时适用的规则相同：

        -   不会记录对事务表的更新。

        -   记录对非事务表的更新，因为回滚不会取消它们。

        -   对事务表和非事务表的混合更新记录在[`开始`](commit.html)和[`回降`](commit.html)这样副本可以进行与源上相同的更改和回滚。

-   可以使用存储过程调用*不*如果从存储函数中调用该过程，则在语句级别写入二进制日志。在这种情况下，记录的唯一内容是调用函数的语句（如果它发生在记录的语句中）或[`做`](do.html)语句（如果发生在未记录的语句中）。因此，在使用调用过程的存储函数时，即使该过程本身是安全的，也应小心谨慎。
