第24章存储的对象

目录

24.1定义存储的程序
24.2使用存储的例程
24.2.1存储的例程语法
24.2.2存储的例程和MySQL权限
24.2.3存储的例程元数据
24.2.4存储过程,函数,触发器和LAST_INSERT_ID()
24.3使用触发器
24.3.1触发语法和示例
24.3.2触发元数据
24.4使用事件调度程序
24.4.1事件调度程序概述
24.4.2事件调度程序配置
24.4.3事件语法
24.4.4事件元数据
24.4.5事件调度程序状态
24.4.6事件调度程序和MySQL权限
24.5使用视图
24.5.1查看语法
24.5.2视图处理算法
24.5.3可更新和可插入的视图
24.5.4带有检查选项的视图子句
24.5.5查看元数据
24.6存储对象访问控制
24.7存储程序二进制日志

本章讨论存储的数据库对象,这些对象是根据存储在服务器上以供以后执行的SQL代码定义的。

存储的对象包括以下对象类型:

本文档中使用的术语反映了存储的对象层次结构:

本章介绍如何使用存储的对象。 以下部分提供有关与这些对象相关的语句的SQL语法以及有关对象处理的其他信息:

24.1定义存储的程序

每个存储的程序都包含一个由SQL语句组成的主体。 此语句可能是由以分号( ; )字符 分隔的多个语句组成的复合语句 例如,以下存储过程的主体由一个 BEGIN ... END 包含 SET 语句 和一个 REPEAT 本身包含另一个 SET 语句 循环组成

CREATE PROCEDURE dorepeat(p1 INT)
开始
  SET @x = 0;
  REPEAT SET @x = @x + 1; UNTIL @x> p1 END REPEAT;
结束;

如果使用 mysql 客户端程序定义包含分号字符的存储程序, 则会 出现问题。 默认情况下, mysql 本身将分号识别为语句分隔符,因此必须临时重新定义分隔符以使 mysql 将整个存储的程序定义传递给服务器。

要重新定义 mysql 分隔符,请使用该 delimiter 命令。 以下示例显示了如何对 dorepeat() 刚刚显示 过程 执行此操作 分隔符更改为 // 以使整个定义作为单个语句传递到服务器,然后 ; 在调用过程之前 还原到 该定义。 这使得 ; 过程体中使用 分隔符能够传递到服务器,而不是由 mysql 本身 解释

MySQL的> delimiter //

mysql> CREATE PROCEDURE dorepeat(p1 INT)
    - > BEGIN
    - >    SET @x = 0;
    - >    REPEAT SET @x = @x + 1; UNTIL @x > p1 END REPEAT;
    - > END
    - >//
查询正常,0行受影响(0.00秒)

MySQL的> delimiter ;

MySQL的> CALL dorepeat(1000);
查询正常,0行受影响(0.00秒)

MySQL的> SELECT @x;
+ ------ +
| @x |
+ ------ +
| 1001 |
+ ------ +
1排(0.00秒)

您可以将分隔符重新定义为除以外的字符串 // ,分隔符可以包含单个字符或多个字符。 您应该避免使用反斜杠( \ )字符,因为这是MySQL的转义字符。

以下是获取参数,使用SQL函数执行操作并返回结果的函数示例。 在这种情况下,不必使用, delimiter 因为函数定义不包含内部 ; 语句分隔符:

mysql> CREATE FUNCTION hello (s CHAR(20))
mysql> RETURNS CHAR(50) DETERMINISTIC
    - >RETURN CONCAT('Hello, ',s,'!');
查询正常,0行受影响(0.00秒)

MySQL的> SELECT hello('world');
+ ---------------- +
| 你好('世界')|
+ ---------------- +
| 你好,世界!|
+ ---------------- +
1排(0.00秒)

24.2使用存储的例程

MySQL支持存储的例程(过程和函数)。 存储例程是一组可以存储在服务器中的SQL语句。 完成此操作后,客户端不需要继续重新发布单个语句,但可以参考存储的例程。

存储例程在某些情况下特别有用:

  • 当多个客户端应用程序以不同语言编写或在不同平台上工作时,需要执行相同的数据库操作。

  • 当安全是至关重要的。 例如,银行使用存储过程和函数进行所有常见操作。 这提供了一致且安全的环境,并且例程可以确保正确记录每个操作。 在这样的设置中,应用程序和用户无法直接访问数据库表,但只能执行特定的存储例程。

存储的例程可以提供改进的性能,因为需要在服务器和客户端之间发送较少的信息。 权衡的是,这确实增加了数据库服务器上的负载,因为更多的工作在服务器端完成,而在客户端(应用程序)端完成的工作则更少。 如果许多客户端计算机(例如Web服务器)仅由一个或几个数据库服务器提供服务,请考虑这一点。

存储的例程还使您可以在数据库服务器中拥有函数库。 这是现代应用程序语言共享的一项功能,可在内部实现此类设计(例如,通过使用类)。 即使在数据库使用范围之外,使用这些客户端应用程序语言功能对程序员也是有益的。

MySQL遵循存储例程的SQL:2003语法,IBM的DB2也使用它。 支持此处描述的所有语法,并在适当的位置记录任何限制和扩展。

其他资源

24.2.1存储的例程语法

存储的例程是过程或函数。 使用 CREATE PROCEDURE CREATE FUNCTION 语句 创建存储例程 (请参见 第13.1.17节“创建过程和创建函数语法” )。 使用 CALL 语句 调用过程 (请参见 第13.2.1节“CALL语法” ),并且只能使用输出变量传回值。 可以从语句内部调用函数,就像任何其他函数一样(即通过调用函数的名称),并且可以返回标量值。 存储例程的主体可以使用复合语句(请参见 第13.6节“复合语句语法” )。

可以使用 DROP PROCEDURE DROP FUNCTION 语句 删除存储的例程 (请参见 第13.1.29节“DROP PROCEDURE和DROP FUNCTION语法” ),并使用 ALTER PROCEDURE ALTER FUNCTION 语句进行 更改 (请参见 第13.1.7节“ALTER PROCEDURE语法” )。

存储过程或函数与特定数据库相关联。 这有几个含义:

  • 调用例程时,将 执行 隐式 (并在例程终止时撤消)。 不允许存储例程中的语句。 USE db_name USE

  • 您可以使用数据库名称限定例程名称。 这可用于引用不在当前数据库中的例程。 例如,要调用 数据库 关联 的存储过程 p 或函数 ,您可以说 f test CALL test.p() test.f()

  • 删除数据库时,也会删除与其关联的所有存储例程。

存储的函数不能递归。

允许存储过程中的递归,但默认情况下禁用。 要启用递归,请将 max_sp_recursion_depth 服务器系统变量设置为大于零的值。 存储过程递归增加了对线程堆栈空间的需求。 如果增加值 max_sp_recursion_depth ,可能需要通过增加 thread_stack 服务器启动时 的值来增加线程堆栈大小 有关 更多信息 请参见 第5.1.8节“服务器系统变量”

MySQL支持一个非常有用的扩展,它允许在 SELECT 存储过程中 使用常规 语句(即,不使用游标或局部变量)。 这种查询的结果集只是直接发送给客户端。 多个 SELECT 语句生成多个结果集,因此客户端必须使用支持多个结果集的MySQL客户端库。 这意味着客户端必须使用MySQL版本的客户端库,至少与4.1版本一样。 客户端还应 CLIENT_MULTI_RESULTS 在连接时 指定 选项。 对于C程序,可以使用 mysql_real_connect() C API函数 完成此操作 请参见 第28.7.7.54节“mysql_real_connect()” 第28.7.23节“C API多语句执行支持”

24.2.2存储的例程和MySQL权限

MySQL授权系统将存储的例程考虑在内,如下所示:

  • CREATE ROUTINE 需要的权限来创建存储例程。

  • ALTER ROUTINE 需要的权限更改或删除保存的程序。 如有必要,此权限将自动授予例程的创建者,并在例程被删除时从创建者中删除。

  • EXECUTE 执行存储例程需要 特权。 但是,如果需要,此特权将自动授予例程的创建者(并在例程被删除时从创建者中删除)。 此外, SQL SECURITY 例程 的默认 特征是 DEFINER ,允许访问与例程关联的数据库的用户执行例程。

  • 如果 automatic_sp_privileges 系统变量为0, EXECUTE 并且 ALTER ROUTINE 权限不会自动授予,并从日常的创造者下降。

  • 例程的创建者是用于为其执行 CREATE 语句 的帐户 这可能 DEFINER 与例程定义中 命名的帐户 不同。

24.2.3存储的例程元数据

有关存储例程的元数据可以通过以下方式获得:

24.2.4存储过程,函数,触发器和LAST_INSERT_ID()

在存储例程(过程或函数)或触发器 LAST_INSERT_ID() 的主体内 更改 的值与 在这些类型的对象的主体外执行的语句 更改方式相同(请参见 第12.15节“信息函数” )。 LAST_INSERT_ID() 以下语句可以看出 存储的例程或触发器对其值的影响 取决于例程的类型:

  • 如果存储过程执行更改值的语句,则更改的值 LAST_INSERT_ID() 将由过程调用之后的语句看到。

  • 对于更改值的存储函数和触发器,当函数或触发器结束时,将恢复该值,因此后续语句不会看到更改的值。

24.3使用触发器

触发器是与表关联的命名数据库对象,并在表发生特定事件时激活。 触发器的一些用途是执行对要插入表中的值的检查,或者对更新中涉及的值执行计算。

触发器定义为在语句插入,更新或删除关联表中的行时激活。 这些行操作是触发事件。 例如,可以通过 INSERT LOAD DATA 语句插入行,并为每个插入的行激活插入触发器。 可以将触发器设置为在触发事件之前或之后激活。 例如,您可以在插入表中的每一行之前或更新的每一行之后激活一个触发器。

重要

MySQL触发器仅针对SQL语句对表所做的更改进行激活。 这包括对可更新视图的基础表的更改。 对于不将SQL语句传输到MySQL服务器的API所做的更改,触发器不会激活。 这意味着使用 NDB API 进行的更新不会激活触发器

更改 INFORMATION_SCHEMA performance_schema 表格 不会激活触发器 这些表实际上是视图上不允许的视图和触发器。

以下部分描述了创建和删除触发器的语法,显示了如何使用它们的一些示例,并指出了如何获取触发器元数据。

其他资源

24.3.1触发语法和示例

要创建触发器或删除触发器,请使用 第13.1.22节“创建触发器语法” 第13.1.34节“DROP TRIGGER语法”中 所述 CREATE TRIGGER DROP TRIGGER 语句

下面是一个简单的示例,它将触发器与表关联起来,以激活 INSERT 操作。 触发器充当累加器,将插入到表的一列中的值相加。

MySQL的> CREATE TABLE account (acct_num INT, amount DECIMAL(10,2));
查询正常,0行受影响(0.03秒)

MySQL的> CREATE TRIGGER ins_sum BEFORE INSERT ON account
       FOR EACH ROW SET @sum = @sum + NEW.amount;
查询OK,0行受影响(0.01秒)

CREATE TRIGGER 语句创建一个 ins_sum 与该 account 关联的 名为的触发器 它还包括指定触发器操作时间,触发事件以及触发器激活时要执行的操作的子句:

  • 关键字 BEFORE 表示触发操作时间。 在这种情况下,触发器在每行插入表格之前激活。 这里允许的另一个关键字是 AFTER

  • 关键字 INSERT 表示触发事件; 也就是说,激活触发器的操作类型。 在该示例中, INSERT 操作导致触发器激活。 您还可以创建触发器 DELETE UPDATE 操作。

  • 以下语句 FOR EACH ROW 定义了触发器主体; 也就是说,每次触发器激活时执行的语句,对于受触发事件影响的每一行都会发生一次。 在该示例中,触发器主体是一个简单的 SET ,它将插入到 amount 列中 的值累积到用户变量中 该语句引用列 NEW.amount ,表示 要插入新行 amount 的值

要使用触发器,请将accumulator变量设置为零,执行一个 INSERT 语句,然后查看该变量后面的值:

mysql> SET @sum = 0;
mysql> INSERT INTO account VALUES(137,14.98),(141,1937.50),(97,-100.00);
mysql>SELECT @sum AS 'Total amount inserted';
+ ----------------------- +
| 插入总量|
+ ----------------------- +
| 1852.48 |
+ ----------------------- +

在这种情况下, 语句执行 @sum 的值 INSERT 14.98 + 1937.50 - 100 ,或 1852.48

要销毁触发器,请使用 DROP TRIGGER 语句。 如果触发器不在默认架构中,则必须指定架构名称:

MySQL的> DROP TRIGGER test.ins_sum;

如果删除表,则表的任何触发器也将被删除。

模式名称空间中存在触发器名称,这意味着所有触发器必须在模式中具有唯一名称。 不同模式中的触发器可以具有相同的名称。

可以为具有相同触发事件和操作时间的给定表定义多个触发器。 例如,您可以 BEFORE UPDATE 为表创建 两个 触发器。 默认情况下,具有相同触发事件和操作时间的触发器按创建顺序激活。 要影响触发器顺序,请在 FOR EACH ROW 指示 之后指定一个子句 FOLLOWS 或者 指定 PRECEDES 具有相同触发事件和操作时间的现有触发器的名称。 使用时 FOLLOWS ,新触发器在现有触发器之后激活。 使用时 PRECEDES ,新触发器在现有触发器之前激活。

例如,以下触发器定义 BEFORE INSERT account 定义了另一个 触发器

MySQL的> CREATE TRIGGER ins_transaction BEFORE INSERT ON account
       FOR EACH ROW PRECEDES ins_sum
       SET
       @deposits = @deposits + IF(NEW.amount>0,NEW.amount,0),
       @withdrawals = @withdrawals + IF(NEW.amount<0,-NEW.amount,0);
查询OK,0行受影响(0.01秒)

此触发器 ins_transaction 类似于 ins_sum 但分别累积存款和取款。 它有一个 PRECEDES 条款,使它在之前激活 ins_sum ; 没有该子句,它会在之后激活, ins_sum 因为它是在之后创建的 ins_sum

在触发器主体中,使用 OLD NEW 关键字可以访问受触发器影响的行中的列。 OLD 并且 NEW 是触发器的MySQL扩展; 它们不区分大小写。

INSERT 触发器中,只能 使用; 没有旧排。 触发器中,只能 使用; 没有新的行。 触发器中,您可以 在更新之前 引用行的列,并在更新后引用行的列。 NEW.col_name DELETE OLD.col_name UPDATE OLD.col_name NEW.col_name

名为的列 OLD 是只读的。 您可以引用它(如果您有 SELECT 权限),但不能修改它。 NEW 如果您拥有该 SELECT 权限,则 可以引用名为的列 BEFORE 触发器中, 如果您拥有该 权限 ,也可以更改其值 这意味着您可以使用触发器来修改要插入新行或用于更新行的值。 (这样的 语句对 触发器 没有影响, 因为行已经发生了变化。) SET NEW.col_name = value UPDATE SET AFTER

在一个 BEFORE 触发器中, NEW 对于一个值 AUTO_INCREMENT 列是0,实际上没有被插入新行时自动生成的序列号。

通过使用该 BEGIN ... END 构造,您可以定义执行多个语句的触发器。 BEGIN 块中,您还可以使用存储例程(例如条件和循环)中允许的其他语法。 但是,就像存储例程一样,如果使用 mysql 程序定义执行多个语句的触发器,则必须重新定义 mysql 语句分隔符,以便可以 ; 在触发器定义中 使用 语句分隔符。 以下示例说明了这些要点。 它定义了一个 UPDATE 触发器检查用于更新每一行的新值,并将值修改为0到100之间的范围。这必须是一个 BEFORE 触发器,因为在用于更新行之前必须检查该值:

mysql> delimiter //
mysql> 
mysql>CREATE TRIGGER upd_check BEFORE UPDATE ON account
       FOR EACH ROW
       BEGIN
           IF NEW.amount < 0 THEN
               SET NEW.amount = 0;
           ELSEIF NEW.amount > 100 THEN
               SET NEW.amount = 100;
           END IF;
       END;//delimiter ;

可以更容易地单独定义存储过程,然后使用简单 CALL 语句 从触发器调用它 如果要从多个触发器中执行相同的代码,这也是有利的。

触发器在激活时执行的语句中可能出现的内容存在限制:

  • 触发器不能使用该 CALL 语句来调用将数据返回给客户端或使用动态SQL的存储过程。 (允许存储过程通过 OUT INOUT 参数 将数据返回到触发器 。)

  • 触发不能使用语句或明或暗地开始或结束交易,如 START TRANSACTION COMMIT ROLLBACK ROLLBACK to SAVEPOINT 被允许,因为它不会结束交易。)。

另请参见 第C.1节“存储程序的限制”

MySQL在触发执行期间处理错误,如下所示:

  • 如果 BEFORE 触发器失败,则不执行相应行上的操作。

  • BEFORE 触发由被激活的 尝试 插入或修改的行,而不管的尝试是否成功随后。

  • 一个 AFTER 只有当任何触发器执行 BEFORE 触发器和行操作成功执行。

  • a BEFORE AFTER 触发 期间的错误 导致导致触发器调用的整个语句失败。

  • 对于事务表,语句失败应导致回滚语句执行的所有更改。 触发器失败会导致语句失败,因此触发器失败也会导致回滚。 对于非事务性表,无法执行此类回滚,因此尽管语句失败,但在错误点之前执行的任何更改仍然有效。

触发器可以按名称包含对表的直接引用,例如 testref 此示例中显示 的命名触发器

CREATE TABLE test1(a1 INT);
CREATE TABLE test2(a2 INT);
CREATE TABLE test3(a3 INT NOT NULL AUTO_INCREMENT PRIMARY KEY);
CREATE TABLE test4(
  a4 INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  b4 INT DEFAULT 0
);

分隔符|

在插入test1之前创建TRIGGER testref
  对于每一行
  开始
    INSERT INTO test2 SET a2 = NEW.a1;
    从test3删除WHERE a3 = NEW.a1;
    UPDATE test4 SET b4 = b4 + 1 WHERE a4 = NEW.a1;
  结束;
|

分隔符;

插入test3(a3)VALUES
  (NULL),(NULL),(NULL),(NULL),(NULL),
  (NULL),(NULL),(NULL),(NULL),(NULL);

插入test4(a4)VALUES
  (0),(0),(0),(0),(0),(0),(0),(0),(0),(0);

假设您将以下值插入表中 test1 ,如下所示:

MySQL的> INSERT INTO test1 VALUES 
       (1), (3), (1), (7), (1), (8), (4), (4);
查询OK,8行受影响(0.01秒)
记录:8个重复:0个警告:0

因此,这四个表包含以下数据:

MySQL的> SELECT * FROM test1;
+ ------ +
| a1 |
+ ------ +
| 1 |
| 3 |
| 1 |
| 7 |
| 1 |
| 8 |
| 4 |
| 4 |
+ ------ +
8行(0.00秒)

MySQL的> SELECT * FROM test2;
+ ------ +
| a2 |
+ ------ +
| 1 |
| 3 |
| 1 |
| 7 |
| 1 |
| 8 |
| 4 |
| 4 |
+ ------ +
8行(0.00秒)

MySQL的> SELECT * FROM test3;
+ ---- +
| a3 |
+ ---- +
| 2 |
| 5 |
| 6 |
| 9 |
| 10 |
+ ---- +
5行(0.00秒)

MySQL的> SELECT * FROM test4;
+ ---- + ------ +
| a4 | b4 |
+ ---- + ------ +
| 1 | 3 |
| 2 | 0 |
| 3 | 1 |
| 4 | 2 |
| 5 | 0 |
| 6 | 0 |
| 7 | 1 |
| 8 | 1 |
| 9 | 0 |
| 10 | 0 |
+ ---- + ------ +
10行(0.00秒)

24.3.2触发元数据

触发器的元数据可以通过以下方式获得:

24.4使用事件调度程序

MySQL的事件调度 管理事件的调度和执行,也就是说,按照时间表运行的任务。 以下讨论涵盖事件调度程序,并分为以下几个部分:

存储例程需要 系统数据库中 events 数据字典表 mysql 此表是在MySQL 8.0安装过程中创建的。 如果要从早期版本升级到MySQL 8.0,请确保执行升级过程以确保您的系统数据库是最新的。 请参见 第2.11节“升级MySQL”

其他资源

24.4.1事件调度程序概述

MySQL事件是根据计划运行的任务。 因此,我们有时将它们称为 预定 事件。 创建事件时,您正在创建一个命名数据库对象,该对象包含一个或多个SQL语句,这些SQL语句将以一个或多个定期间隔执行,从特定日期和时间开始和结束。 从概念上讲,这类似于Unix crontab (也称为 cron作业 )或Windows任务计划程序的概念。

这种类型的计划任务有时也称为 时间触发器 ,暗示这些是由时间推移触发的对象。 虽然这基本上是正确的,但我们更喜欢使用术语 事件 来避免与 第24.3节“使用触发器”中 讨论的类型的触发器混淆 事件应该更具体地不与 临时触发器 混淆 触发器是数据库对象,其语句是响应于在给定表上发生的特定类型的事件而执行的,((已调度的)事件是响应于指定时间间隔的通过而执行其语句的对象。

虽然SQL标准中没有提供事件调度的规定,但在其他数据库系统中也有先例,您可能会注意到这些实现与MySQL服务器中的实现之间存在一些相似之处。

MySQL Events具有以下主要特性和属性:

  • 在MySQL中,事件由其名称和分配给它的模式唯一标识。

  • 事件根据计划执行特定操作。 此操作由一个SQL语句组成, BEGIN ... END 如果需要 ,该语句可以是 块中 的复合语句 (请参见 第13.6节“复合语句语法” )。 事件的时间可以是 一次性 反复发作 一次性事件仅执行一次。 周期性事件以固定间隔重复其动作,并且可以为周期性事件的时间表分配特定的开始日期和时间,结束日期和时间,两者或两者都不分配。 (默认情况下,定期事件的计划在创建后立即开始,并且无限期地继续,直到它被禁用或删除。)

    如果重复事件未在其调度间隔内终止,则结果可能是同时执行事件的多个实例。 如果这是不合需要的,您应该建立一个机制来防止同时发生。 例如,您可以使用 GET_LOCK() 函数,或行或表锁定。

  • 用户可以使用用于这些目的的SQL语句创建,修改和删除预定事件。 语法无效的事件创建和修改语句失败,并显示相应的错误消息。 用户可以在事件的动作中包括需要用户实际上没有的特权的语句 事件创建或修改语句成功,但事件的操作失败。 有关详细 信息, 请参见 第24.4.6节“事件调度程序和MySQL权限”

  • 可以使用SQL语句设置或修改事件的许多属性。 这些属性包括事件的名称,计时,持久性(即,是否在其计划到期后保留),状态(启用或禁用),要执行的操作以及分配给它的模式。 请参见 第13.1.3节“ALTER EVENT语法”

    事件的默认定义者是创建事件的用户,除非事件已被更改,在这种情况下,定义者是发出 ALTER EVENT 影响该事件 的最后一个 语句 的用户 任何具有 EVENT 定义事件的数据库特权的 用户都可以修改 事件。 请参见 第24.4.6节“事件调度程序和MySQL权限”

  • 事件的操作语句可能包括存储例程中允许的大多数SQL语句。 有关限制,请参见 第C.1节“存储程序的限制”

24.4.2事件调度程序配置

事件由特殊 事件调度程序线程执行 ; 当我们引用事件调度程序时,我们实际上是指这个线程。 运行时,具有 PROCESS 输出权限 的用户可以看到事件调度程序线程及其当前状态 SHOW PROCESSLIST ,如下面的讨论所示。

全局 event_scheduler 系统变量确定是否在服务器上启用并运行事件调度程序。 它具有这3个值中的一个,这会影响事件调度,如此处所述。 默认是 ON

  • ON :事件计划程序已启动; 事件调度程序线程运行并执行所有计划事件。

    当事件调度程序是 ON ,事件调度程序线程在 SHOW PROCESSLIST 作为守护进程 的输出中列出 ,其状态如下所示:

    MySQL的> SHOW PROCESSLIST\G
    *************************** 1。排******************** *******
         Id:1
       用户:root
       主持人:localhost
         db:NULL
    命令:查询
       时间:0
      状态:NULL
       信息:show processlist
    *************************** 2.排******************** *******
         Id:2
       用户:event_scheduler
       主持人:localhost
         db:NULL
    命令:守护进程
       时间:3
      州:等待下一次激活
       信息:NULL
    2行(0.00秒)
    

    可以通过设置 event_scheduler to 的值来停止事件调度 OFF

  • OFF :事件计划程序已停止。 事件调度程序线程未运行,未在输出中显示 SHOW PROCESSLIST ,并且未执行任何预定事件。

    当事件调度停止( event_scheduler OFF ),它可以通过设置的值开始 event_scheduler ON (见下一个项目。)

  • DISABLED :此值使事件计划程序不可操作。 事件调度程序是 DISABLED ,事件调度程序线程不运行(因此不会出现在输出中 SHOW PROCESSLIST )。 此外,无法在运行时更改事件计划程序状态。

如果事件调度程序状态尚未设置为 DISABLED event_scheduler 则可以在 ON 之间切换 OFF (使用 SET )。 也可以使用 0 OFF ,而 1 对于 ON 设置该变量时。 因此,可以在 mysql 客户端中 使用以下4个语句中的任何一个 来打开事件调度程序:

SET GLOBAL event_scheduler = ON;
SET @@ GLOBAL.event_scheduler = ON;
SET GLOBAL event_scheduler = 1;
SET @@ GLOBAL.event_scheduler = 1;

同样,这4个语句中的任何一个都可用于关闭事件调度程序:

SET GLOBAL event_scheduler = OFF;
SET @@ GLOBAL.event_scheduler = OFF;
SET GLOBAL event_scheduler = 0;
SET @@ GLOBAL.event_scheduler = 0;

虽然 ON OFF 具有数字等同物,显示该值 event_scheduler 通过 SELECT SHOW VARIABLES 总是之一 OFF ON DISABLED DISABLED 没有数字等价物 出于这个原因, ON 并且 OFF 通常是优选的超过 1 0 设置此变量时。

请注意,尝试设置 event_scheduler 而不将其指定为全局变量会导致错误:

mysql < ERROR 1229(HY000):变量'event_scheduler'是GLOBALSET @@event_scheduler = OFF;

变量,应该用SET GLOBAL设置
重要

可以将事件调度程序设置为 DISABLED 仅在服务器启动时。 如果 event_scheduler ON OFF ,则无法 DISABLED 在运行时 将其设置为 此外,如果将事件计划程序设置为 DISABLED 启动时,则无法更改 event_scheduler 运行时 的值

要禁用事件调度程序,请使用以下两种方法之一:

  • 作为启动服务器时的命令行选项:

    --event调度= DISABLED
    
  • 在服务器配置文件( my.cnf my.ini Windows系统)中,包括服务器将读取它的行(例如,在一 [mysqld] 节中):

    event_scheduler = DISABLED
    

要启用事件计划程序,请在不使用 --event-scheduler=DISABLED 命令行选项的 情况下重新启动服务器 ,或者在删除或注释掉 event-scheduler=DISABLED 服务器配置文件中 包含的行后 (如果适用)。 或者,您可以 在启动服务器时 使用 ON (或 1 )或 OFF (或 0 )代替 DISABLED 值。

注意

当您可以发出事件操作语句 event_scheduler 设置为 DISABLED 在这种情况下不会产生警告或错误(前提是这些语句本身有效)。 但是,在将此变量设置为 ON (或 1 之前,无法执行预定事件 完成此操作后,事件调度程序线程将执行其调度条件满足的所有事件。

使用该 --skip-grant-tables 选项 启动MySQL服务器 会导致 event_scheduler 设置为 DISABLED ,覆盖命令行 my.cnf my.ini 文件 中的任何其他值集 (Bug#26807)。

有关用于创建,更改和删除事件的SQL语句,请参见 第24.4.3节“事件语法”

MySQL EVENTS INFORMATION_SCHEMA 数据库中 提供了一个 可以查询此表以获取有关已在服务器上定义的预定事件的信息。 有关更多信息 请参见 第24.4.4节“事件元数据” 第25.10节“INFORMATION_SCHEMA事件表”

有关事件调度​​和MySQL权限系统的信息,请参见 第24.4.6节“事件调度程序和MySQL权限”

24.4.3事件语法

MySQL提供了几个用于处理预定事件的SQL语句:

24.4.4事件元数据

有关事件的元数据可以通过以下方式获得:

事件调度程序时间表示

MySQL中的每个会话都有一个会话时区(STZ)。 这是 会话开始时 time_zone 从服务器的全局 time_zone 初始化 的会话 值, 但可以在会话期间更改。

执行 CREATE EVENT or ALTER EVENT 语句 时的当前会话时区 用于解释事件定义中指定的时间。 这成为事件时区(ETZ); 也就是说,用于事件调度的时区,它在执行时在事件中生效。

对于在事件信息表示 mysql.event 表中, execute_at starts ,和 ends 时间被转换为UTC和与事件的时区一起存储。 这使得事件执行能够按照定义继续执行,而不管服务器时区或夏令时效果的任何后续更改。 last_executed 时间也存储在UTC。

如果您从中选择信息 mysql.event ,则刚刚提到的时间将被检索为UTC值。 这些时间也可以通过从 INFORMATION_SCHEMA.EVENTS 表中 选择 或从中获得 SHOW EVENTS ,但它们被报告为ETZ值。 从这些来源获得的其他时间表示事件创建或最后更改的时间; 这些显示为STZ值。 下表总结了事件时间的表示。

mysql.event INFORMATION_SCHEMA.EVENTS SHOW EVENTS
执行于 世界标准时间 ETZ ETZ
启动 世界标准时间 ETZ ETZ
世界标准时间 ETZ ETZ
最后执行 世界标准时间 ETZ N / A
创建 STZ STZ N / A
最后改变了 STZ STZ N / A

24.4.5事件调度程序状态

事件调度程序将有关事件执行的信息写入MySQL服务器的错误日志,该信息以错误或警告终止。 有关 示例, 请参见 第24.4.6节“事件调度程序和MySQL权限”

要获取有关事件调度​​程序状态的信息以进行调试和故障排除,请运行 mysqladmin debug (请参见 第4.5.2节“ mysqladmin - 管理MySQL服务器的客户端” ); 运行此命令后,服务器的错误日志包含与事件调度程序相关的输出,类似于此处显示的内容:

活动状态:
LLA =最后锁定在LUA =最后解锁时间
WOC =等待条件DL =数据锁定

事件调度程序状态:
状态:INITIALIZED
线程ID:0
LLA:init_scheduler:313
LUA:init_scheduler:318
WOC:没有
工人:0
执行:0
数据已锁定:否

事件队列状态:
元素数:1
数据已锁定:否
试图锁定:没有
LLA:init_queue:148
LUA:init_queue:168
WOC:没有
下一次激活:0000-00-00 00:00:00

在作为事件调度程序执行的事件的一部分发生的语句中,诊断消息(不仅是错误,还包括警告)将写入错误日志,并在Windows上写入应用程序事件日志。 对于频繁执行的事件,可能会导致许多记录的消息。 例如,对于 语句,如果查询不返回任何行,则会发生错误代码为1329的警告( ),并且变量值保持不变。 如果查询返回多行,则发生错误1172( )。 对于任何一种情况,您都可以通过声明条件处理程序来避免记录警告; 请参见 第13.6.7.2节“DECLARE ... HANDLER语法” SELECT ... INTO var_list No data Result consisted of more than one row 对于可能检索多行的语句,另一种策略是使用 LIMIT 1 将结果集限制为单行。

24.4.6事件调度程序和MySQL权限

要启用或禁用已调度事件的执行,必须设置全局 event_scheduler 系统变量的值。 这需要足以设置全局系统变量的权限。 请参见 第5.1.9.1节“系统变量权限”

EVENT 权限控制事件的创建,修改和删除。 可以使用此权限 GRANT 例如,此 GRANT 语句赋予 用户 EVENT 指定的模式 特权 myschema jon@ghidora

关于myschema的重大事件。* to jon @ ghidora;

(我们假设此用户帐户已存在,并且我们希望它保持不变。)

要授予此用户 EVENT 对所有模式 特权,请使用以下语句:

授予活动*。*至jon @ ghidora;

EVENT 特权具有全局或模式级范围。 因此,尝试在单个表上授予它会导致错误,如下所示:

mysql> ERROR 1144(42000):非法GRANT / REVOKE命令; GRANT EVENT ON myschema.mytable TO jon@ghidora;

请参阅手册以了解可以使用的权限

重要的是要理解事件是以其定义者的特权执行的,并且它不能执行其定义者没有必要特权的任何操作。 例如,假设 jon@ghidora EVENT 权限 myschema 还假设此用户具有 此架构 SELECT 权限 myschema ,但没有其他权限。 可以 jon@ghidora 创建一个新事件,例如:

创建事件e_store_ts
    按照时间表
      每10秒
      INSERT INTO myschema.mytable VALUES(UNIX_TIMESTAMP());

用户等待一分钟左右,然后执行 SELECT * FROM mytable; 查询,期望在表中看到几个新行。 相反,该表是空的。 由于用户没有 INSERT 相关表 权限,因此该事件无效。

如果检查MySQL错误日志( hostname.err ),您可以看到事件正在执行,但它尝试执行的操作失败:

2013-09-24T12:41:31.261992Z 25 [ERROR]事件调度程序:
[jon @ ghidora] [cookbook.e_store_ts] INSERT命令被拒绝给用户
表'mytable'''jon'@'ghidora'
2013-09-24T12:41:31.262022Z 25 [注意]事件调度程序:
[jon @ ghidora]。[myschema.e_store_ts]事件执行失败。
2013-09-24T12:41:41.271796Z 26 [ERROR]事件调度程序:
[jon @ ghidora] [cookbook.e_store_ts] INSERT命令被拒绝给用户
表'mytable'''jon'@'ghidora'
2013-09-24T12:41:41.272761Z 26 [注意]事件调度程序:
[jon @ ghidora]。[myschema.e_store_ts]事件执行失败。

由于此用户很可能无法访问错误日志,因此可以通过直接执行它来验证事件的操作语句是否有效:

mysql> ERROR 1142(42000):拒绝用户INSERT命令INSERT INTO myschema.mytable VALUES (UNIX_TIMESTAMP());

表'mytable'''jon'@'ghidora'

检查 INFORMATION_SCHEMA.EVENTS 表显示 e_store_ts 存在并已启用,但其 LAST_EXECUTED 列为 NULL

MySQL的> SELECT * FROM INFORMATION_SCHEMA.EVENTS
     >      WHERE EVENT_NAME='e_store_ts'
     >     AND EVENT_SCHEMA='myschema'\G
*************************** 1。排******************** *******
   EVENT_CATALOG:NULL
    EVENT_SCHEMA:myschema
      EVENT_NAME:e_store_ts
         DEFINER:jon @ ghidora
      EVENT_BODY:SQL
EVENT_DEFINITION:INSERT INTO myschema.mytable VALUES(UNIX_TIMESTAMP())
      EVENT_TYPE:RECURRING
      EXECUTE_AT:NULL
  INTERVAL_VALUE:5
  INTERVAL_FIELD:第二
        SQL_MODE:NULL
          STARTS:0000-00-00 00:00:00
            ENDS:0000-00-00 00:00:00
          状态:已启用
   ON_COMPLETION:NOT PRESERVE
         创建时间:2006-02-09 22:36:06
    LAST_ALTERED:2006-02-09 22:36:06
   LAST_EXECUTED:NULL
   EVENT_COMMENT:
1排(0.00秒)

要取消该 EVENT 权限,请使用该 REVOKE 语句。 在此示例中, 将从 用户帐户中 删除 EVENT 架构 权限 myschema jon@ghidora

在myschema上重播活动。*来自jon @ ghidora;
重要

撤消 EVENT 用户 权限不会删除或禁用该用户可能创建的任何事件。

由于重命名或删除创建事件的用户,不会迁移或删除事件。

假设用户 jon@ghidora 已被授予 EVENT INSERT 特权上的 myschema 架构。 然后,此用户创建以下事件:

创建事件e_insert
    按照时间表
      每7秒
      INSERT INTO myschema.mytable;

创建此事件后, root 撤消该 EVENT 权限 jon@ghidora 但是, e_insert 继续执行, mytable 每7秒 插入一个新行 如果 root 发布以下任何一种陈述,情况也是如此:

  • DROP USER jon@ghidora;

  • RENAME USER jon@ghidora TO someotherguy@ghidora;

您可以通过 在发出 声明 之前和之后 检查 mysql.event 表(在本节后面讨论)或 INFORMATION_SCHEMA.EVENTS 表(请参见 第25.10节“INFORMATION_SCHEMA事件表” 来验证这是否成立 DROP USER RENAME USER

事件定义存储在 mysql.event 表中。 要删除由其他用户帐户创建的事件,MySQL root 用户(或具有必要权限的其他用户)可以从此表中删除行。 例如,要删除 e_insert 前面显示 的事件 root 可以使用以下语句:

从mysql.event删除
    WHERE db ='myschema'
      AND name ='e_insert';

mysql.event 表中 删除行时,匹配事件名称和数据库模式名称非常重要 这是因为不同模式中可能存在同名的不同事件。

用户 EVENT 权限存储在 Event_priv 列中 在这两种情况下,此列都包含其中一个值' '或' '。 ' '是默认值。 仅当该用户具有全局 权限(即,如果使用该权限时 ,则为 给定用户 设置为' ' 对于模式级 权限, 创建一个行 并将该行的 设置 为模式的名称,列为 用户名称 列,以及 mysql.user mysql.db Y N N mysql.user.Event_priv Y EVENT GRANT EVENT ON *.* EVENT GRANT mysql.db Db User Event_priv 列设置为' Y ”。 永远不需要直接操作这些表,因为 语句对它们执行所需的操作。 GRANT EVENT REVOKE EVENT

五个状态变量提供与事件相关的操作的计数(但 包括事件执行的语句;请参见 第C.1节“存储程序的限制” )。 这些是:

  • Com_create_event CREATE EVENT 自上次服务器重新启动以来执行 语句

  • Com_alter_event ALTER EVENT 自上次服务器重新启动以来执行 语句

  • Com_drop_event DROP EVENT 自上次服务器重新启动以来执行 语句

  • Com_show_create_event SHOW CREATE EVENT 自上次服务器重新启动以来执行 语句

  • Com_show_events SHOW EVENTS 自上次服务器重新启动以来执行 语句

您可以通过运行语句一次查看所有这些的当前值 SHOW STATUS LIKE '%event%';

24.5使用视图

MySQL支持视图,包括可更新视图。 视图是存储的查询,在调用时会生成结果集。 视图充当虚拟表。

以下讨论描述了创建和删除视图的语法,并显示了如何使用它们的一些示例。

其他资源

24.5.1查看语法

CREATE VIEW 语句创建一个新视图(请参见 第13.1.23节“创建视图语法” )。 要更改视图的定义或删除视图,请使用 ALTER VIEW (请参见 第13.1.11节“ALTER VIEW语法” )或 DROP VIEW (参见 第13.1.35节“DROP VIEW语法” )。

可以从多种 SELECT 语句 创建视图 它可以引用基表或其他视图。 它可以使用连接 UNION 和子查询。 SELECT 甚至不需要引用任何表。 以下示例定义了一个视图,该视图从另一个表中选择两列,以及从这些列计算的表达式:

mysql> CREATE TABLE t (qty INT, price INT);
mysql> INSERT INTO t VALUES(3, 50), (5, 60);
mysql> CREATE VIEW v AS SELECT qty, price, qty*price AS value FROM t;
mysql>SELECT * FROM v;
+ ------ + ------- ------- + +
| 数量| 价格| 价值|
+ ------ + ------- ------- + +
| 3 | 50 | 150 |
| 5 | 60 | 300 |
+ ------ + ------- ------- + +
MySQL的> SELECT * FROM v WHERE qty = 5;
+ ------ + ------- ------- + +
| 数量| 价格| 价值|
+ ------ + ------- ------- + +
| 5 | 60 | 300 |
+ ------ + ------- ------- + +

24.5.2视图处理算法

可选 ALGORITHM 子句 CREATE VIEW 或是 ALTER VIEW 标准SQL的MySQL扩展。 它会影响MySQL处理视图的方式。 ALGORITHM 三个值: MERGE TEMPTABLE UNDEFINED

  • 对于 MERGE ,引用视图和视图定义的语句的文本被合并,以便视图定义的部分替换语句的相应部分。

  • 对于 TEMPTABLE ,视图的结果将被检索到临时表中,然后用于执行该语句。

  • 因为 UNDEFINED ,MySQL选择使用哪种算法。 它喜欢 MERGE TEMPTABLE 如果可能的话,因为 MERGE 通常是更高效的并且因为如果使用临时表的视图不能更新。

  • 如果不存在 ALGORITHM 子句,则默认算法由 系统变量 derived_merge 标志 值确定 optimizer_switch 有关其他讨论,请参见 第8.2.2.4节“使用合并或实现优化派生表,视图引用和公用表表达式”

TEMPTABLE 明确 指定的一个原因 是,在创建临时表之后以及在用于完成处理语句之前,可以在基础表上释放锁。 这可能导致比 MERGE 算法 更快的锁定释放, 因此使用该视图的其他客户端不会被阻止。

视图算法有 UNDEFINED 三个原因:

  • 声明中 没有 ALGORITHM 条款 CREATE VIEW

  • CREATE VIEW 声明有明确的 ALGORITHM = UNDEFINED 条款。

  • ALGORITHM = MERGE 为只能使用临时表处理的视图指定。 在这种情况下,MySQL会生成警告并将算法设置为 UNDEFINED

如前所述, MERGE 通过将视图定义的相应部分合并到引用视图的语句中来处理。 以下示例简要说明了 MERGE 算法的工作原理。 这些示例假设有一个 v_merge 具有此定义 的视图

CREATE ALGORITHM = MERGE VIEW v_merge(vc1,vc2)AS
SELECT c1,c2 FROM t WHERE c3> 100;

示例1:假设我们发出以下声明:

SELECT * FROM v_merge;

MySQL处理语句如下:

  • v_merge t

  • * 成为 vc1, vc2 ,对应于 c1, c2

  • 视图 WHERE 子句已添加

生成的语句将变为:

SELECT c1,c2 FROM t WHERE c3> 100;

示例2:假设我们发出以下声明:

SELECT * FROM v_merge WHERE vc1 <100;

这种说法也同样处理前一个,只是 vc1 < 100 变得 c1 < 100 和视图 WHERE 子句添加到语句 WHERE 使用条款 AND 结缔组织(并添加括号以确保条款的部分用正确的优先顺序执行)。 生成的语句将变为:

SELECT c1,c2 FROM t WHERE(c3> 100)AND(c1 <100);

实际上,要执行的 WHERE 语句具有以下形式 子句:

WHERE(选择WHERE)AND(查看WHERE)

如果 MERGE 无法使用 算法,则必须使用临时表。 阻止合并的构造与阻止在派生表和公用表表达式中合并的构造相同。 示例是 SELECT DISTINCT LIMIT 在子查询中。 有关详细信息,请参见 第8.2.2.4节“使用合并或实现优化派生表,视图引用和公用表表达式”

24.5.3可更新和可插入的视图

某些视图是可更新的,对它们的引用可用于指定要在数据更改语句中更新的表。 也就是说,你可以在语句,如使用它们 UPDATE DELETE INSERT 更新基础表的内容。 派生表和公用表表达式也可以在多表 UPDATE DELETE 语句中 指定 ,但只能用于读取数据以指定要更新或删除的行。 通常,视图引用必须是可更新的,这意味着它们可以合并而不是实现。 复合视图具有更复杂的规则。

要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。 还有一些其他构造使视图不可更新。 更具体地说,如果视图包含以下任何内容,则视图不可更新:

  • 聚集函数或窗函数( SUM() MIN() MAX() COUNT() ,等等)

  • DISTINCT

  • GROUP BY

  • HAVING

  • UNION 要么 UNION ALL

  • 选择列表中的子查询

    选择列表中的非依赖子查询失败 INSERT ,但可以 UPDATE DELETE 对于选择列表中的从属子查询,不允许使用任何数据更改语句。

  • 某些连接(请参阅本节后面的其他连接讨论)

  • 引用 FROM 子句中的 nonupdatable视图

  • 子查询的 WHERE 子句是指表在 FROM 条款

  • 仅指文字值(在这种情况下,没有要更新的基础表)

  • ALGORITHM = TEMPTABLE (使用临时表总是使视图不可更新)

  • 对基表的任何列的多次引用(失败 INSERT ,好的 UPDATE DELETE

视图中生成的列被视为可更新,因为可以为其分配。 但是,如果明确更新此类列,则唯一允许的值为 DEFAULT 有关生成的列的信息,请参见 第13.1.20.9节“创建表和生成的列”

假设可以使用 MERGE 算法 处理多表视图,有时可以更新多表视图 为此,视图必须使用内部联接(不是外部联接或a UNION )。 此外,只能更新视图定义中的单个表,因此该 SET 子句必须只列出视图中其中一个表的列。 UNION ALL 即使它们在理论上可以更新 也不允许 使用 它们。

关于可插入性(可使用 INSERT 语句 进行更新 ),如果可更新视图也满足视图列的这些附加要求,则可插入视图:

  • 必须没有重复的视图列名称。

  • 视图必须包含基表中没有默认值的所有列。

  • 视图列必须是简单的列引用。 它们不能是表达式,例如:

    3.14159
    col1 + 3
    UPPER(COL2)
    col3 / col4subquery

MySQL在 CREATE VIEW 时间 设置了一个标志,称为视图可更新性标志 YES 如果 UPDATE DELETE (和类似操作)对视图合法,则 该标志设置为 (true) 否则,该标志设置为 NO (false)。 表中 IS_UPDATABLE INFORMATION_SCHEMA.VIEWS 显示此标志的状态。 这意味着服务器始终知道视图是否可更新。

如果视图是不可更新的,这样的语句 UPDATE DELETE 以及 INSERT 是非法的,将被拒绝。 (即使视图是可更新的,也可能无法插入其中,如本节其他部分所述。)

视图的可更新性可能受 updatable_views_with_limit 系统变量 值的影响 请参见 第5.1.8节“服务器系统变量”

对于以下讨论,假设存在以下表和视图:

CREATE TABLE t1(x INTEGER);
CREATE TABLE t2(c INTEGER);
创建视图vmat AS SELECT SUM(x)AS s FROM t1;
创建视图vup AS SELECT * FROM t2;
CREATE VIEW vjoin AS SELECT * FROM vmat JOIN vup on vmat.s = vup.c;

INSERT UPDATE DELETE 语句允许如下:

  • INSERT INSERT 语句 的插入表 可以是合并的视图引用。 如果视图是连接视图,则视图的所有组件都必须是可更新的(未实现)。 对于多表可更新视图, INSERT 如果它插入到单个表中 ,则 可以工作。

    此语句无效,因为连接视图的一个组件是不可更新的:

    插入vjoin(c)VALUES(1);
    

    本声明有效; 视图不包含实体化组件:

    插入vup(c)VALUES(1);
    
  • UPDATE :要在 UPDATE 语句中 更新的一个或多个表 可能是合并的视图引用。 如果视图是连接视图,则视图的至少一个组件必须是可更新的(这与之不同 INSERT )。

    在多表 UPDATE 语句中,语句的更新表引用必须是基表或可更新的视图引用。 非更新表引用可以是物化视图或派生表。

    本声明有效; column c 来自连接视图的可更新部分:

    更新vjoin SET c = c + 1;
    

    该声明无效; x 来自nonupdatable部分:

    更新vjoin SET x = x + 1;
    

    本声明有效; 多表的更新表引用 UPDATE 是可更新的视图( vup ):

    UPDATE vup JOIN(SELECT SUM(x)AS s FROM t1)AS dt ON ...
    SET c = c + 1;
    

    该声明无效; 它尝试更新具体化的派生表:

    UPDATE vup JOIN(SELECT SUM(x)AS s FROM t1)AS dt ON ...
    SET s = s + 1;
    
  • DELETE :要在 DELETE 语句中 删除的一个或多个表 必须是合并视图。 不允许加入视图(这与 INSERT 不同 UPDATE )。

    此语句无效,因为视图是连接视图:

    删除vjoin WHERE ...;
    

    此语句有效,因为视图是合并(可更新)视图:

    删除vup WHERE ...;
    

    此语句有效,因为它从合并(可更新)视图中删除:

    删除vup FROM vup JOIN(SELECT SUM(x)AS s FROM t1)AS dt ON ...;
    

其他讨论和示例如下。

本节中的早期讨论指出,如果并非所有列都是简单的列引用,则视图不可插入(例如,如果它包含表达式或复合表达式的列)。 虽然这样的视图不可插入,但如果只更新非表达式的列,则可以更新。 考虑这个观点:

CREATE VIEW v AS SELECT col1,1 AS col2 FROM t;

此视图不可插入,因为它 col2 是一个表达式。 但是,如果更新没有尝试更新,则可以更新 col2 此更新是允许的:

UPDATE v SET col1 = 0;

此更新是不允许的,因为它尝试更新表达式列:

UPDATE v SET col2 = 0;

如果表包含 AUTO_INCREMENT 列,则插入表中不 包含 列的可插入视图 AUTO_INCREMENT 不会更改值 LAST_INSERT_ID() ,因为将默认值插入不属于视图的列的副作用不应该是可见的。

24.5.4带有检查选项的视图子句

WITH CHECK OPTION 条款可以给出一个可更新视图来防止插入到行的量, WHERE 在该条款 select_statement 是不正确的。 它还会阻止更新 WHERE 子句为true的 行, 但更新会导致它不为true(换句话说,它会阻止可见行更新为不可见的行)。

WITH CHECK OPTION 可更新视图 子句中, LOCAL CASCADED 关键字确定在根据另一个视图定义视图时检查测试的范围。 如果没有给出关键字,则默认为 CASCADED

WITH CHECK OPTION 测试符合标准:

  • 使用 LOCAL ,查看view WHERE 子句,然后检查recurses到基础视图并应用相同的规则。

  • 使用 CASCADED ,查看view WHERE 子句,然后检查recurses到底层视图,添加 WITH CASCADED CHECK OPTION 它们(为了检查;它们的定义保持不变),并应用相同的规则。

  • 如果没有检查选项,则不检查view WHERE 子句,然后检查recurses到基础视图,并应用相同的规则。

考虑下表和视图集的定义:

CREATE TABLE t1(INT);
CREATE VIEW v1 AS SELECT * FROM t1 WHERE a <2
WITH CHECK OPTION;
CREATE VIEW v2 AS SELECT * FROM v1 WHERE a> 0
WITH LOCAL CHECK OPTION;
CREATE VIEW v3 AS SELECT * FROM v1 WHERE a> 0
带有级联检查选项;

这里 v2 v3 视图是根据另一个视图定义的 v1

v2 根据其 LOCAL 检查选项 检查 插入 ,然后检查递归到 v1 并再次应用规则。 v1 导致检查失败 的规则 检查 v3 也失败:

MySQL的> INSERT INTO v2 VALUES (2);
ERROR 1369(HY000):检查选项失败'test.v2'
MySQL的> INSERT INTO v3 VALUES (2);
ERROR 1369(HY000):检查选项失败'test.v3'

24.5.5查看元数据

有关视图的元数据可以获得如下:

24.6存储对象访问控制

存储的程序(过程,函数,触发器和事件)和视图在使用之前定义,并且在引用时,在确定其特权的安全上下文中执行。 这些权限由其 DEFINER 属性和 SQL SECURITY 特征控制。

DEFINER属性

所有存储的对象定义都可以包含一个 DEFINER 命名MySQL帐户 属性。 如果定义省略了该 DEFINER 属性,则默认定义者是创建该对象的用户。

MySQL使用以下规则来控制用户可以在对象 DEFINER 属性中 指定的帐户

  • 如果您具有 SET_USER_ID SUPER 权限,则可以将任何帐户指定为 DEFINER 值,但如果该帐户不存在则会生成警告。 此外,从MySQL 8.0.16开始,要将 DEFINER 存储对象 属性 设置 为具有该 SYSTEM_USER 权限 的帐户 ,您必须具有该 SYSTEM_USER 权限。

  • 否则,只允许账户是你自己的,无论是字面规定或作为 CURRENT_USER CURRENT_USER() 您无法将定义者设置为其他帐户。

使用不存在的 DEFINER 帐户 创建存储对象 可能会产生负面后果:

  • 对于存储例程,如果 SQL SECURITY 值为 DEFINER 但定义者帐户不存在, 则在例程执行时发生错误

  • 对于触发器,在帐户确实存在之前触发激活不是一个好主意。 否则,未定义与权限检查有关的行为。

  • 对于事件,如果该帐户不存在,则在事件执行时发生错误。

  • 对于视图,如果 SQL SECURITY 值为 DEFINER 但是定义者帐户不存在, 则在引用视图时会发生错误

SQL安全特性

存储例程(过程和函数)和视图的定义可以包括 SQL SECURITY 具有值的特征 DEFINER INVOKER 指定对象是在定义者还是调用者上下文中执行的特征。 如果定义省略了 SQL SECURITY 特征,则默认为定义上下文。

触发器和事件没有 SQL SECURITY 特征,总是在definer上下文中执行。 服务器根据需要自动调用这些对象,因此没有调用用户。

定义者和调用者安全上下文的不同之处如下:

  • 在定义安全上下文中执行的存储对象使用其 DEFINER 属性 命名的帐户的权限执行 这些权限可能与调用用户的权限完全不同。 调用者必须具有引用对象的适当权限(例如, EXECUTE 调用存储过程或 SELECT 从视图中选择),但在对象执行期间,调用者的权限将被忽略,只有 DEFINER 帐户权限才重要。 如果该 DEFINER 帐户具有很少的权限,则该对象在其可以执行的操作中相应地受到限制。 如果 DEFINER 如果帐户具有高权限(例如 root 帐户),则 无论谁调用它 ,该对象都可以执行强大的操作

  • 在调用程序安全性上下文中执行的存储例程或视图只能执行调用者具有特权的操作。 DEFINER 在对象执行期间, 属性无效。

例子

考虑以下存储过程,声明 SQL SECURITY DEFINER 为在definer安全上下文中执行:

CREATE DEFINER ='admin'@'localhost'PROCEDURE p1()
SQL安全定义器
开始
  UPDATE t1 SET counter = counter + 1;
结束;

拥有该 EXECUTE 权限的 任何用户都 p1 可以使用 CALL 语句 调用它 但是, p1 执行时,它会在definer安全上下文中执行,因此 'admin'@'localhost' 将使用 DEFINER 属性中 指定的帐户 的权限执行 此帐户必须具有 EXECUTE 的权限 p1 ,以及在 UPDATE 为表特权 t1 的对象身体内引用。 否则,该过程失败。

现在考虑这个存储过程, p1 除了它的 SQL SECURITY 特性是 INVOKER

CREATE DEFINER ='admin'@'localhost'PROCEDURE p2()
SQL安全调查员
开始
  UPDATE t1 SET counter = counter + 1;
结束;

调用安全上下文中的执行 不同 p1 p2 因此无论 DEFINER 属性值 如何,都可以使用调用用户的特权 p2 如果调用者缺少表的 EXECUTE 特权 p2 UPDATE 特权,则 失败 t1

风险最小化指南

要最大程度地降低存储对象创建和使用的潜在风险,请遵循以下准则:

  • 对于存储的例程或视图, SQL SECURITY INVOKER 尽可能在对象定义中 使用 ,以便只有具有适合对象执行的操作的权限的用户才能使用它。

  • 如果在使用具有 SET_USER_ID or或 SUPER 权限 的帐户时创建definer-context存储对象 ,请指定一个显式 DEFINER 属性,该属性命名仅拥有对象执行的操作所需的权限的帐户。 DEFINER 仅在绝对必要时 指定高权限 帐户。

  • 管理员可以 DEFINER 通过不授予用户 SET_USER_ID SUPER 权限 来阻止用户创建指定高权限 帐户的 存储对象

  • 应该写入定义者上下文对象,记住他们可能能够访问调用用户没有权限的数据。 在某些情况下,您可以通过不向未授权用户授予特定权限来阻止对这些对象的引用:

    • 没有 EXECUTE 特权 的用户不能引用存储的例程

    • 没有相应权限的用户不能引用该视图( SELECT 从中进行选择, INSERT 插入其中等等)。

    但是,触发器和事件不存在这样的控制,因为它们总是在definer上下文中执行。 服务器根据需要自动调用这些对象; 用户不直接引用它们:

    • 触发器通过访问与之关联的表来激活,甚至是没有特殊权限的用户进行的普通表访问。

    • 服务器按计划执行事件。

    在这两种情况下,如果 DEFINER 帐户具有高权限,则该对象可能能够执行敏感或危险操作。 如果从创建对象的用户的帐户中撤消创建对象所需的权限,则仍然如此。 管理员应特别注意授予用户对象创建权限。

24.7存储程序二进制日志

二进制日志包含有关修改数据库内容的SQL语句的信息。 该信息以 描述修改 事件 的形式存储 (二进制日志事件与预定事件存储对象不同。)二进制日志有两个重要目的:

  • 对于复制,二进制日志在主复制服务器上用作要发送到从属服务器的语句的记录。 主服务器将其二进制日志中包含的事件发送到其从属服务器,这些服务器执行这些事件以对主服务器上的数据进行相同的更改。 请参见 第17.2节“复制实现”

  • 某些数据恢复操作需要使用二进制日志。 还原备份文件后,将重新执行备份后记录的二进制日志中的事件。 这些事件使数据库从备份点更新。 请参见 第7.3.2节“使用备份进行恢复”

但是,如果在语句级别进行日志记录,则存储的程序(存储过程和函数,触发器和事件)存在某些二进制日志记录问题:

  • 在某些情况下,语句可能会影响主服务器和从服务器上的不同行集。

  • 在从属服务器上执行的复制语句由具有完全权限的从属SQL线程处理。 程序可以在主服务器和从服务器上遵循不同的执行路径,因此用户可以编写包含危险语句的例程,该例程仅在从服务器上执行,并由具有完全权限的线程处理。

  • 如果修改数据的存储程序是不确定的,则它是不可重复的。 这可能导致主站和从站上的数据不同,或导致恢复的数据与原始数据不同。

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

通常,当在SQL语句级别(基于语句的二进制日志记录)发生二进制日志记录时,会导致此处描述的问题。 如果使用基于行的二进制日志记录,则日志包含由于执行SQL语句而对各个行所做的更改。 执行例程或触发器时,将记录行更改,而不是进行更改的语句。 对于存储过程,这意味着 CALL 不记录 语句。 对于存储的函数,将记录函数内的行更改,而不是函数调用。 对于触发器,将记录触发器所做的行更改。 在从属端,只能看到行更改,而不是存储的程序调用。

混合格式二进制日志记录( binlog_format=MIXED )使用基于语句的二进制日志记录,但只保证基于行的二进制日志记录能够产生正确结果的情况除外。 对于混合格式,当存储的函数,存储过程,触发器,事件或预准备语句包含对基于语句的二进制日志记录不安全的任何内容时,整个语句将标记为不安全并以行格式记录。 用于创建和删除过程,函数,触发器和事件的语句始终是安全的,并以语句格式记录。 有关基于行,混合和基于语句的日志记录以及如何确定安全和不安全语句的详细信息,请参见 第17.2.1节“复制格式”

除非另有说明,否则此处的备注假定在服务器上启用了二进制日志记录(请参见 第5.4.4节“二进制日志” 。)如果未启用二进制日志,则无法进行复制,二进制日志也不可用于数据恢复。

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

  • 要创建或更改存储的函数, 除了 通常需要的权限 权限 之外 ,还必须具有 SET_USER_ID SUPER 权限 (取决于 函数定义中 值, 或者 可能需要,无论是否启用二进制日志记录。请参见 第13.1.17节“创建过程和创建函数语法” 。) CREATE ROUTINE ALTER ROUTINE DEFINER SET_USER_ID SUPER

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

    默认情况下,一个 CREATE FUNCTION 语句被接受,至少一个 DETERMINISTIC NO SQL READS SQL DATA 必须明确指定。 否则会发生错误:

    ERROR 1418(HY000):此函数没有DETERMINISTIC,NO SQL,
    或启用声明和二进制日志记录中的READS SQL DATA
    (你*可能*想要使用安全性较低的log_bin_trust_function_creators
    变量)
    

    此功能是确定性的(并且不会修改数据),因此它是安全的:

    创建功能f1(i INT)
    退货INT
    确定性
    读取SQL数据
    开始
      回归我;
    结束;
    

    此函数使用 UUID() ,这不是确定性的,因此该函数也不是确定性的并且不安全:

    创建功能f2()
    返回CHAR(36)CHARACTER SET utf8
    开始
      RETURN UUID();
    结束;
    

    此功能修改数据,因此可能不安全:

    创建功能f3(p_id INT)
    退货INT
    开始
      UPDATE t SET modtime = NOW()WHERE id = p_id;
      RETURN ROW_COUNT();
    结束;
    

    评估函数的性质是基于 创建者 诚实 MySQL不检查声明的函数 DETERMINISTIC 是否没有产生非确定性结果的语句。

  • 当您尝试执行存储函数时,如果 binlog_format=STATEMENT 已设置,则 DETERMINISTIC 必须在函数定义中指定关键字。 如果不是这种情况,则会生成错误并且函数不会运行,除非 log_bin_trust_function_creators=1 指定覆盖此检查(参见下文)。 对于递归函数调用, DETERMINISTIC 仅在最外层调用时需要 关键字。 如果正在使用基于行或混合二进制日志记录,则即使在没有 DETERMINISTIC 关键字的 情况下定义函数,也会接受并复制该语句

  • 因为MySQL在创建时不检查函数是否确实是确定性的,所以使用 DETERMINISTIC 关键字 调用存储函数 可能会执行对基于语句的日志记录不安全的操作,或者调用包含不安全语句的函数或过程。 如果在 binlog_format=STATEMENT 设置 时发生这种情况 ,则会发出警告消息。 如果正在使用基于行或混合二进制日志记录,则不会发出警告,并且将以基于行的格式复制语句。

  • 要放松函数创建的前述条件(必须具有 SUPER 权限并且必须将函数声明为确定性或不修改数据),请将全局 log_bin_trust_function_creators 系统变量 设置 为1.默认情况下,此变量的值为0,但是你可以改变它:

    MySQL的> SET GLOBAL log_bin_trust_function_creators = 1;
    

    您还 --log-bin-trust-function-creators=1 可以在启动服务器时 使用该 选项 设置此变量

    如果未启用二进制日志记录, log_bin_trust_function_creators 则不适用。 SUPER 函数创建不需要,除非如前所述, DEFINER 函数定义中 值需要它。

  • 有关可能对复制不安全的内置函数的信息(从而导致使用它们的存储函数也不安全),请参见 第17.4.1节“复制功能和问题”

触发器与存储函数类似,因此前面关于函数的注释也适用于具有以下异常的触发器: CREATE TRIGGER 没有可选 DETERMINISTIC 特性,因此假定触发器始终是确定性的。 但是,在某些情况下,这种假设可能无效。 例如,该 UUID() 函数是不确定的(并且不会复制)。 在触发器中使用此类功能时要小心。

触发器可以更新表,因此 CREATE TRIGGER 如果您没有所需的权限, 则会出现类似于存储函数的错误消息 在从属端,从属设备使用trigger DEFINER 属性来确定哪个用户被认为是触发器的创建者。

本节的其余部分提供了有关日志记录实现及其含义的其他详细信息。 除非您对存储的常规使用中当前与日志记录相关的条件的基本原理感兴趣,否则无需阅读它。 此讨论仅适用于基于语句的日志记录,而不适用于基于行的日志记录,但第一项除外: CREATE 并且 DROP 语句将记录为语句,而不管日志记录模式如何。

  • 服务器写入 CREATE EVENT CREATE PROCEDURE CREATE FUNCTION ALTER EVENT ALTER PROCEDURE ALTER FUNCTION DROP EVENT DROP PROCEDURE ,和 DROP FUNCTION 语句二进制日志。

  • SELECT 如果函数更改数据并且在不会记录的语句中发生,则将 存储的函数调用记录为 语句。 这可以防止因在非记录语句中使用存储函数而导致的数据更改不重复。 例如, SELECT 语句不会写入二进制日志,但是 SELECT 可能会调用存储的函数进行更改。 要处理此问题, 当给定函数进行更改时,会将语句写入二进制日志。 假设在master上执行以下语句: SELECT func_name()

    CREATE FUNCTION f1(INT)RETURNS INT
    开始
      如果(a <3)那么
        插入t2值(a);
      万一;
      返回0;
    结束;
    
    CREATE TABLE t1(INT);
    插入t1值(1),(2),(3);
    
    SELECT f1(a)FROM t1;
    

    SELECT 语句执行,函数 f1() 被调用三次。 其中两个调用插入一行,MySQL SELECT 为每个 调用记录一个 语句。 也就是说,MySQL将以下语句写入二进制日志:

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

    SELECT 当函数调用导致错误的存储过程时, 服务器还会记录 存储函数调用 语句。 在这种情况下,服务器将 SELECT 语句与预期的错误代码一起 写入 日志。 在从属设备上,如果发生相同的错误,那就是预期的结果并继续复制。 否则,复制将停止。

  • 记录存储的函数调用而不是函数执行的语句对复制具有安全隐患,这可能源于两个因素:

    • 函数可以遵循主服务器和从服务器上的不同执行路径。

    • 在从属服务器上执行的语句由具有完全权限的从属SQL线程处理。

    这意味着虽然用户必须具有 CREATE ROUTINE 创建函数 权限,但是用户可以编写包含危险语句的函数,该函数仅在具有完全特权的线程处理它的从服务器上执行。 例如,如果主服务器和从属服务器的服务器ID值分别为1和2,则主服务器上的用户可以创建和调用不安全的函数 unsafe_func() ,如下所示:

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

    CREATE FUNCTION INSERT 语句被写入二进制日志,以便从机将执行它们。 因为从属SQL线程具有完全权限,所以它将执行危险语句。 因此,函数调用对主服务器和从服务器具有不同的影响,并且不是复制安全的。

    为了防止启用了二进制日志记录的服务器的这种危险 SUPER ,除了通常 CREATE ROUTINE 所需的权限 之外 ,存储的函数创建者必须具有该 特权 同样,要使用 ALTER FUNCTION ,您必须拥有该 SUPER 权限之外的 ALTER ROUTINE 权限。 没有该 SUPER 权限,将发生错误:

    错误1419(HY000):您没有SUPER权限
    启用二进制日志记录(您*可能*希望使用不太安全的日志
    log_bin_trust_function_creators变量)
    

    如果您不希望要求函数创建者拥有该 SUPER 特权(例如,如果 CREATE ROUTINE 您系统上 具有该 特权的 所有用户 都是有经验的应用程序开发人员),请将全局 log_bin_trust_function_creators 系统变量 设置 为1.您还可以使用该 --log-bin-trust-function-creators=1 选项 设置此变量 启动服务器时。 如果未启用二进制日志记录, log_bin_trust_function_creators 则不适用。 SUPER 函数创建不需要,除非如前所述, DEFINER 函数定义中 值需要它。

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

    • 它会使奴隶与主人不同。

    • 恢复的数据将与原始数据不同。

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

    • DETERMINISTIC NOT DETERMINISTIC 特性指示的功能是否总是产生对于给定的输入相同的结果。 默认值是, NOT DETERMINISTIC 如果没有给出特征。 要声明函数是确定性的,必须 DETERMINISTIC 明确 指定

    • CONTAINS SQL NO SQL READS SQL DATA ,和 MODIFIES SQL DATA 特征提供有关该功能是否读取或写入数据信息。 无论是 NO SQL READS SQL DATA 指示功能不会改变的数据,但由于默认情况下是必须指定的这些明确的一个 CONTAINS SQL ,如果没有特性给予。

    默认情况下,一个 CREATE FUNCTION 语句被接受,至少一个 DETERMINISTIC NO SQL READS SQL DATA 必须明确指定。 否则会发生错误:

    ERROR 1418(HY000):此函数没有DETERMINISTIC,NO SQL,
    或启用声明和二进制日志记录中的READS SQL DATA
    (你*可能*想要使用安全性较低的log_bin_trust_function_creators
    变量)
    

    如果设置 log_bin_trust_function_creators 为1,则会删除功能为确定性或不修改数据的要求。

  • 存储过程调用记录在语句级别而不是 CALL 级别。 也就是说,服务器不记录 CALL 语句,它会在实际执行的过程中记录这些语句。 因此,将在从属服务器上观察到主服务器上发生的相同更改。 这可以防止可能由于在不同计算机上具有不同执行路径的过程而导致的问题。

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

    • 要记录的语句可能包含对本地过程变量的引用。 这些变量不存在于存储过程上下文之外,因此无法从字面上记录引用此类变量的语句。 相反,每个对局部变量的引用都被此构造替换以用于记录目的:

      NAME_CONST(var_namevar_value

      var_name 是局部变量名称, var_value 是一个常量,指示变量在记录语句时的值。 NAME_CONST() 具有的价值 var_value ,以及 var_name 因此,如果直接调用此函数,则会得到如下结果:

      MySQL的> SELECT NAME_CONST('myname', 14);
      + -------- +
      | myname |
      + -------- +
      | 14 |
      + -------- +
      

      NAME_CONST() 允许在从属服务器上执行已记录的独立语句,其效果与在存储过程中在主服务器上执行的原始语句具有相同的效果。

      当源列表达式引用局部变量时, 使用 NAME_CONST() 可能导致 CREATE TABLE ... SELECT 语句出现 问题 将这些引用转换为 NAME_CONST() 表达式可能会导致主服务器和从属服务器上的列名不同,或者名称太长而不能成为合法的列标识符。 解决方法是为引用局部变量的列提供别名。 myvar 值为1 时,请考虑此语句

      CREATE TABLE t1 SELECT myvar;
      

      这将改写如下:

      CREATE TABLE t1 SELECT NAME_CONST(myvar,1);
      

      要确保主表和从表具有相同的列名,请按以下方式编写语句:

      CREATE TABLE t1选择myvar AS myvar;
      

      重写的声明变为:

      CREATE TABLE t1 SELECT NAME_CONST(myvar,1)AS myvar;
      
    • 要记录的语句可能包含对用户定义变量的引用。 为了解决这个问题,MySQL将一条 SET 语句 写入 二进制日志,以确保变量存在于从站上,其值与主站上的值相同。 例如,如果语句引用变量 @my_var ,则该语句将在二进制日志中由以下语句开头,其中 value @my_var master 的值

      SET @my_var = value;
      
    • 过程调用可以在已提交或回滚的事务中进行。 考虑事务上下文,以便正确复制过程执行的事务方面。 也就是说,服务器日志中发现实际执行和修改数据的过程中这些语句,还记录 BEGIN COMMIT ROLLBACK 语句是必要的。 例如,如果过程仅更新事务表并在回滚的事务中执行,则不会记录这些更新。 如果过程发生在已提交的事务中, BEGIN 并且 COMMIT 使用更新记录语句。 对于在回滚事务中执行的过程,如果语句以独立方式执行,则使用相同的规则记录其语句:

      • 不记录事务表的更新。

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

      • 对事务和非事务表混合的更新将被记录为包围 BEGIN ROLLBACK 以便从服务器进行与主服务器相同的更改和回滚。

  • 如果从存储函数中调用 过程,则 不会 在语句级别 将存储过程调用 写入二进制日志。 在这种情况下,唯一记录的是调用函数的语句(如果它发生在记录的语句中)或 DO 语句(如果它发生在未记录的语句中)。 因此,在使用调用过程的存储函数时应该小心,即使该过程本身是安全的。

原文