二进制日志包含关于修改数据库内容的SQL语句的信息。此信息以的形式存储”事件”描述了修改。(二进制日志事件不同于计划事件存储对象。)二进制日志有两个重要的目的:
对于复制,在源复制服务器上使用二进制日志作为要发送到复制服务器的语句的记录。源将其二进制日志中包含的事件发送到其副本,副本执行这些事件以进行与源上所做的相同的数据更改。看到第16.2节“复制实现”.
某些数据恢复操作需要使用二进制日志。恢复备份文件后,将重新执行备份之后记录的二进制日志中的事件。这些事件将数据库从备份点更新到最新状态。看到第7.3.2节,“使用备份进行恢复”.
但是,如果日志记录发生在语句级别,则对于存储程序(存储过程和函数、触发器和事件)存在某些二进制日志记录问题:
在某些情况下,一条语句可能会影响源和副本上的不同行集。
在副本上执行的复制语句由拥有完全权限的副本SQL线程处理。一个过程可以在源服务器和副本服务器上遵循不同的执行路径,因此用户可以编写一个包含危险语句的例程,该语句只在副本上执行,该副本由具有完全权限的线程处理。
如果修改数据的存储程序是不确定的,那么它是不可重复的。这可能导致源和副本上的数据不同,或者导致恢复的数据与原始数据不同。
本节描述MySQL如何处理存储程序的二进制日志记录。它说明了当前实现对存储程序的使用所施加的条件,以及您可以做些什么来避免日志问题。它还提供了关于这些条件的原因的附加信息。
除非另有说明,这里的说明假定服务器上启用了二进制日志记录(请参阅第5.4.4节“二进制日志”)。如果没有启用二进制日志,则无法进行复制,也无法使用二进制日志进行数据恢复。在MySQL 5.7中,默认情况下二进制日志记录是不启用的,您可以使用——log-bin
选择。
通常,当二进制日志记录发生在SQL语句级别(基于语句的二进制日志记录)时,就会出现这里描述的问题。如果使用基于行的二进制日志记录,则日志将包含执行SQL语句时对各个行所做的更改。执行例程或触发器时,记录行更改,而不是进行更改的语句。对于存储过程,这意味着调用
语句未被记录。对于存储的函数,将记录函数内所做的行更改,而不是函数调用。对于触发器,由触发器所做的行更改将被记录。在副本端,只能看到行更改,而不能看到存储的程序调用。
混合格式二进制日志记录(binlog_format =混合
)使用基于语句的二进制日志记录,除非只有基于行的二进制日志记录才能保证得到正确的结果。使用混合格式,当存储函数、存储过程、触发器、事件或准备语句包含任何对基于语句的二进制日志记录不安全的内容时,整个语句将被标记为不安全并以行格式记录。用于创建和删除过程、函数、触发器和事件的语句总是安全的,并以语句格式记录。有关基于行、混合和基于语句的日志记录,以及如何确定安全语句和不安全语句的详细信息,请参见第16.2.1节“复制格式”.
MySQL中存储函数的使用条件可以总结如下。这些条件不适用于存储过程或事件调度器事件,除非启用二进制日志记录,否则不适用。
要创建或更改存储函数,必须具有
超级
特权,除了创建程序
或改变日常
通常需要的特权。(视乎定义者
函数定义中的值,超级
无论是否启用二进制日志记录,都可能需要。看到第13.1.16节,CREATE PROCEDURE和CREATE FUNCTION语句)。创建存储函数时,必须声明它是确定性函数,或者声明它不修改数据。否则可能导致数据恢复或复制不安全。
默认情况下,对于a
创建函数
陈述要被接受,至少要有一个确定的
,没有SQL
,或读取SQL数据
必须显式指定。否则将发生错误:错误1418 (HY000):这个函数在它的声明中没有DETERMINISTIC, NO SQL,或READS SQL DATA,并且启用了二进制日志记录(你*可能*想使用不安全的log_bin_trust_function_creators变量)
这个函数是确定的(并且不修改数据),所以它是安全的:
创建函数f1(i INT)返回确定的读取SQL数据开始返回i结束;
此函数使用
UUID ()
,这是不确定性的,所以函数也是不确定性的,是不安全的:创建函数f2()返回字符集utf8开始返回UUID();结束;
这个函数修改数据,所以可能不安全:
创建函数f3(p_id INT)返回INT BEGIN UPDATE t SET modtime = NOW() WHERE id = p_id;返回ROW_COUNT ();结束;
函数性质的评估是基于”诚实”创造者。MySQL不检查函数是否声明
确定的
不包含产生不确定结果的语句。当尝试执行存储函数时,如果
binlog_format =声明
,则确定的
关键字必须在函数定义中指定。如果不是这种情况,则会生成一个错误,并且函数不会运行,除非log_bin_trust_function_creators = 1
指定重写此检查(请参阅下面)。对于递归函数调用,确定的
关键字只在最外层调用时是必需的。如果使用了基于行或混合二进制日志记录,则即使函数定义时没有使用确定的
关键字。因为MySQL在创建时不检查函数是否确实是确定性的,所以使用
确定的
关键字可能执行对基于语句的日志记录不安全的操作,或调用包含不安全语句的函数或过程。如果这种情况发生在binlog_format =声明
设置时,将发出警告消息。如果正在使用基于行或混合二进制日志记录,则不会发出警告,并以基于行的格式复制语句。要放宽前面关于函数创建的条件(即必须具有
超级
特权和函数必须声明为确定性或不修改数据),设置全局log_bin_trust_function_creators
系统变量为1。默认情况下,这个变量的值为0,但是你可以这样修改它:mysql> SET GLOBAL log_bin_trust_function_creators = 1;
您也可以在服务器启动时设置此变量。
如果没有启用二进制日志记录,
log_bin_trust_function_creators
不适用。超级
函数创建时不需要定义者
函数定义中的值需要它。有关可能对复制不安全(从而导致使用它们的存储函数也不安全)的内置函数的信息,请参见第16.4.1节“复制特性和问题”.
触发器类似于存储函数,所以前面关于函数的说明也适用于触发器,但有以下例外:创建触发器
没有可选的吗确定的
特征,所以触发器被假定总是确定性的。然而,这种假设在某些情况下可能是无效的。例如,UUID ()
函数是不确定的(并且不复制)。在触发器中使用此类函数时要小心。
触发器可以更新表,因此会出现与存储函数类似的错误消息创建触发器
如果您没有所需的权限。在副本端,副本使用触发器定义者
属性确定将哪个用户视为触发器的创建者。
本节的其余部分将提供关于日志实现及其含义的更多详细信息。您不需要阅读它,除非您对存储例程使用中当前与日志相关的条件的基本原理感兴趣。本讨论仅适用于基于语句的日志记录,而不适用于基于行的日志记录,第一项除外:创建
而且下降
无论日志记录模式如何,语句都被记录为语句。
服务器写入
创建事件
,创建过程
,创建函数
,改变事件
,改变的过程
,改变函数
,删除事件
,下降过程
,删除函数
语句添加到二进制日志。存储的函数调用被记录为
选择
如果函数更改数据并且发生在不会被记录的语句中,则声明。这可以防止在非日志语句中使用存储的函数而导致的数据更改的非复制。例如,选择
语句不是写入二进制日志,而是写入a选择
可能会调用进行更改的存储函数。要处理这个问题,a选择
语句将在给定函数进行更改时写入二进制日志。假设在源服务器上执行以下语句:func_name
()创建函数f1(a INT)返回INT BEGIN IF (a < 3) THEN INSERT INTO t2 VALUES (a)如果;返回0;结束;创建表t1 (INT);插入t1 VALUES (1),(2),(3);SELECT f1(a) FROM t1;
当
选择
语句执行时,函数f1 ()
被调用三次。其中两次调用插入一行,MySQL记录a选择
语句。也就是说,MySQL将以下语句写入二进制日志:选择f1 (1);选择f1 (2);
服务器也会记录日志
选择
当函数调用导致错误的存储过程时,用于存储函数调用的语句。在本例中,服务器写入选择
语句连同预期的错误代码一起发送到日志中。在副本上,如果发生相同的错误,这就是预期的结果,复制将继续。否则,复制将停止。记录存储的函数调用而不是函数执行的语句会对复制产生安全影响,这源于两个因素:
函数可以在源服务器和副本服务器上遵循不同的执行路径。
在副本上执行的语句由拥有完全权限的副本SQL线程处理。
其含义是,尽管用户必须具有
创建程序
特权创建一个函数,用户可以编写一个包含危险语句的函数,该函数只在副本上执行,该副本由具有完全特权的线程处理。例如,如果源服务器和副本服务器的服务器ID值分别为1和2,则源服务器上的用户可以创建和调用不安全的函数unsafe_func ()
如下:mysql> delimiter // mysql> CREATE FUNCTION unsafe_func () RETURNS INT -> BEGIN -> IF @@server_id=2 THENdangerous_statement;如果;->返回1;- >结束;-> // mysql>分隔符;INSERT INTO t VALUES(unsafe_func());
的
创建函数
而且插入
语句被写入二进制日志,因此副本执行它们。因为复制SQL线程拥有完全的权限,所以它执行危险的语句。因此,函数调用对源和副本有不同的影响,并且不是复制安全的。为防止启用二进制日志记录的服务器出现这种危险,存储函数创建者必须具有
超级
特权,除了平常创建程序
必需的特权。类似地,使用改变函数
,你必须有超级
特权之外的改变日常
特权。没有超级
特权,发生错误:错误1419 (HY000):您没有SUPER权限并且启用了二进制日志记录(您*可能*想使用不太安全的log_bin_trust_function_creators变量)
如果您不希望要求函数创建者具有
超级
权限(例如,如果所有具有创建程序
特权对您的系统有经验的应用程序开发人员),设置全局log_bin_trust_function_creators
系统变量为1。您也可以在服务器启动时设置此变量。如果没有启用二进制日志记录,log_bin_trust_function_creators
不适用。超级
函数创建时不需要定义者
函数定义中的值需要它。如果执行更新的函数是不确定的,那么它是不可重复的。这可能会产生两种不良影响:
它使副本与源文件不同。
恢复后的数据与原数据不一致。
为了处理这些问题,MySQL强制执行以下要求:在源服务器上,除非你声明函数是确定的或不修改数据,否则将拒绝函数的创建和更改。这里有两组函数特征:
的
确定的
而且不确定性
特征表示函数对于给定的输入是否总是产生相同的结果。默认为不确定性
如果没有给出特征。要声明一个函数是确定的,必须指定确定的
明确。的
包含SQL
,没有SQL
,读取SQL数据
,修改SQL数据
特征提供关于函数是否读取或写入数据的信息。要么没有SQL
或读取SQL数据
指示函数不更改数据,但必须显式指定其中之一,因为默认值为包含SQL
如果没有给出特征。
默认情况下,对于a
创建函数
陈述要被接受,至少要有一个确定的
,没有SQL
,或读取SQL数据
必须显式指定。否则将发生错误:错误1418 (HY000):这个函数在它的声明中没有DETERMINISTIC, NO SQL,或READS SQL DATA,并且启用了二进制日志记录(你*可能*想使用不安全的log_bin_trust_function_creators变量)
如果你设置
log_bin_trust_function_creators
到1,函数是确定的或不修改数据的要求被删除。存储过程调用是在语句级别记录的,而不是在
调用
的水平。即服务器不记录日志调用
语句,它在实际执行的过程中记录这些语句。因此,在副本上可以观察到源服务器上发生的相同更改。这可以防止由于过程在不同机器上具有不同的执行路径而导致的问题。通常,在存储过程中执行的语句将使用与以独立方式执行语句相同的规则写入二进制日志。在记录过程语句时需要特别注意,因为在过程中的语句执行与在非过程上下文中不完全相同:
要记录的语句可能包含对本地过程变量的引用。这些变量在存储过程上下文之外不存在,因此不能逐字记录引用此类变量的语句。相反,为了记录日志,每个对局部变量的引用都会被这个结构所取代:
NAME_CONST (var_name,var_value)
var_name
是局部变量名,和var_value
常量,指示记录语句时变量的值。NAME_CONST ()
值为var_value
,以及”的名字”的var_name
.因此,如果你直接调用这个函数,你会得到这样的结果:mysql> SELECT name ('myname', 14);+--------+ | 的名字 | +--------+ | 14 | +--------+
NAME_CONST ()
允许在副本上执行已记录日志的独立语句,其效果与在存储过程中的源上执行的原始语句相同。使用
NAME_CONST ()
会导致问题吗创建表…选择
语句,当源列表达式引用局部变量时。将这些引用转换为NAME_CONST ()
表达式可能导致源服务器和副本服务器上的列名不同,或者名称太长而不是合法的列标识符。一种变通方法是为引用局部变量的列提供别名。在以下情况下考虑这句话myvar#
值为1:CREATE TABLE t1 SELECT myvar
改写如下:
SELECT NAME_CONST(myvar, 1);
为了确保源表和复制表有相同的列名,写这样的语句:
SELECT myvar作为myvar
重写后的语句为:
SELECT NAME_CONST(myvar, 1) AS myvar;
要记录的语句可能包含对用户定义变量的引用。为了处理这个问题,MySQL编写了一个
集
语句,以确保该变量存在于副本上,且值与源上的值相同。例如,如果语句引用一个变量@my_var
,该语句在二进制日志中前面是下面的语句,其中价值
的值@my_var
关于来源:SET @my_var =价值;
过程调用可以发生在已提交或回滚的事务中。考虑到事务上下文,以便正确地复制过程执行的事务方面。也就是说,服务器在实际执行和修改数据的过程中记录这些语句,并记录日志
开始
,提交
,回滚
必要时的陈述。例如,如果过程仅更新事务表,并且在回滚的事务中执行,则不会记录这些更新。如果该过程发生在已提交的事务中,开始
而且提交
语句与更新一起记录。对于在回滚事务中执行的过程,其语句将使用与以独立方式执行语句相同的规则进行记录:
存储过程调用是不如果从存储函数中调用过程,则在语句级别写入二进制日志。在这种情况下,唯一被记录的是调用函数的语句(如果它发生在被记录的语句中)或
做
语句(如果它发生在未记录的语句中)。因此,在使用调用过程的存储函数时应格外小心,即使过程本身在其他方面是安全的。