MySQL/存储程序
MySQL 支持一些对 SQL 的过程扩展。通过使用它们,您可以管理控制流、创建循环和使用游标。这些功能允许您创建存储程序,它们可以是三种类型:
- 触发器 - 在某个事件涉及表之前/之后触发的程序(DELETE、INSERT、UPDATE);
- 事件 - 定期在一段时间间隔后执行的程序;
- 存储过程 - 可以通过 CALL SQL 命令调用的程序。
MySQL 的未来版本将支持用其他语言编写的存储程序,而不仅仅是 SQL。您将能够管理新的语言作为 PLUGIN。此外,存储过程将被编译成 C 代码,因此它们将更快。
触发器在 MySQL 5.0.2 中添加。它们在持久表上工作,但不能与 TEMPORARY 表相关联。
创建新的触发器
CREATE TRIGGER `delete_old` AFTER INSERT ON `articles`
FOR EACH ROW BEGIN
DELETE FROM `articles` ORDER BY `id` ASC LIMIT 1
END
这个示例触发器定义了一个名为 `delete_old` 的存储程序(它是一个简单的 DELETE 语句)。它在将新记录 INSERT 到 `articles` 时自动触发。它是在 INSERT 之后调用,而不是之前。如果单个 INSERT 将多行添加到表中,`delete_old` 将被调用多次。这个想法很简单:当创建新记录时,最旧的记录会被 DELETEd。
触发器可以在某个 SQL 语句之前或之后执行。这很重要,因为触发器可以执行一个或多个激活其他触发器的语句;因此,决定它们的执行顺序可能很重要,以确保数据库的完整性。
触发触发器的语句必须是基本的 DML 命令
- INSERT,包括 LOAD DATA 和 REPLACE
- DELETE,包括 REPLACE,但不包括 TRUNCATE
- UPDATE
特殊情况是 INSERT ... ON DUPLICATE KEY UPDATE。如果执行了 INSERT,则 BEFORE INSERT 和 AFTER INSERT 都会执行。如果 INSERT 未执行,并且因此执行了 UPDATE,则事件顺序如下:BEFORE INSERT、BEFORE UPDATE、AFTER UPDATE。
您也可以使用以下语法指定表名
... ON `my_database`.`my_table` ...
触发器名称在数据库中必须是唯一的。同一个数据库中的两个表不能关联到具有相同名称的两个不同触发器。
与其他 DBMS 和标准 SQL 不同,所有触发器都针对每行触发,并且不能针对每个语句执行。
存储程序必须指定在 BEGIN 和 END 保留字之间。您不能在此处使用动态 SQL(PREPARE 语句);相反,可以使用调用存储过程。如果您只执行一条语句,可以省略 BEGIN 和 END 字。
您可以访问字段的旧值(它在语句执行之前的价值)以及新值(它在语句执行之后的值)。示例
CREATE TRIGGER `use_values` AFTER INSERT ON `example_tab`
FOR EACH ROW BEGIN
UPDATE `changelog` SET `old_value`=OLD.`field1`, `new_value`=NEW.`field1` WHERE `backup_tab`.`id`=`example_tab`.`id`
END
要 DROP 触发器,您可以使用以下语法
DROP TRIGGER `my_trigger`
或者
DROP TRIGGER `my_database`.`my_trigger`
或者
DROP TRIGGER IF EXISTS `my_trigger`
要更改现有触发器,您必须 DROP 并重新创建它。
此命令返回用于创建触发器的 CREATE TRIGGER 语句,以及有关可能影响语句的设置的一些信息。
SHOW CREATE TRIGGER delete_old;
- 触发器 - 触发器名称
- sql_mode - 语句执行时的 SQL_MODE 值
- SQL 原始语句
- character_set_client
- collation_connection
- 数据库排序规则
此语句在 MySQL 5.1 中添加。
如果您想获取当前数据库中所有触发器的列表,可以键入以下内容
SHOW TRIGGERS
如果您想获取另一个数据库中包含的触发器的列表,可以使用
SHOW TRIGGERS IN `my_db`
SHOW TRIGGERS FROM `my_db` -- synonym
如果您想列出名称与 LIKE 表达式匹配的触发器
SHOW TRIGGERS FROM `my_db` LIKE 'my_%'
更复杂的过滤器
SHOW TRIGGERS WHERE table='users'
您不能将 LIKE 和 WHERE 同时使用。
此语句返回的列为
- 触发器 - 触发器的名称
- 事件 - 触发触发器的 SQL 命令
- 表 - 与触发器关联的表
- 语句 - 触发器执行的语句
- 时间 - BEFORE 或 AFTER
- 创建 - 它始终为 NULL
- sql_mode - 创建触发器时设置的 SQL_MODE
- 定义者 - 创建触发器的用户
- character_set_client - 创建触发器时的 `character_set_client` 变量的值
- collation_connection - 创建触发器时的 `collation_connection` 变量的值
- 数据库排序规则 - 数据库(和触发器)使用的排序规则
INFORMATION_SCHEMA 虚拟数据库有一个 `TRIGGERS` 表。它具有以下字段
- TRIGGER_CATALOG - 哪个目录包含触发器(尚未实现)
- TRIGGER_SCHEMA - 哪个 SCHEMA(DATABASE)包含触发器
- TRIGGER_NAME - 触发器的名称
- EVENT_MANIPULATION - INSERT / UPDATE /DELETE
- EVENT_OBJECT_CATALOG - 尚未实现
- EVENT_OBJECT_SCHEMA - 包含与触发器关联的表的 SCHEMA
- EVENT_OBJECT_NAME - 与触发器关联的表的名称
- ACTION_ORDER - 尚未实现
- ACTION_CONDITION - 尚未实现
- ACTION_STATEMENT - 触发器激活时要执行的语句
- ACTION_ORIENTATION - 尚未实现
- ACTION_TIMING - BEFORE / AFTER
- ACTION_REFERENCE_OLD_TABLE - 未实现
- ACTION_REFERENCE_NEW_TABLE - 未实现
- ACTION_REFERENCE_OLD_ROW - 未实现
- ACTION_REFERENCE_NEW_ROW - 未实现
- 创建 - 创建时间(尚未实现)
- SQL_MODE - 此触发器执行的有效 SQL_MODE
- 定义者 - 创建触发器的用户,格式为 'user@host'
- CHARACTER_SET_CLIENT - 创建触发器时的 `character_set_client` 变量的值
- COLLATION_CONNECTION - 创建触发器时的 `collation_connection` 变量的值
- DATABASE_COLLATION - 数据库(和触发器)使用的排序规则
事件也称为计划事件或时间触发器。它们是计划的事件,在特定时间或特定时间间隔内执行。它们类似于 UNIX crontab。
一旦事件启动,就必须完全执行。如果在事件执行结束之前重新激活它,则会创建同一个事件的新实例。如果可能发生这种情况,使用锁来确保数据一致性可能是一个好主意。
事件调度器是一个线程,它永久地处于执行状态。它在事件必须启动时启动事件。如果您不需要事件,您可以禁用事件调度器。您可以使用以下选项启动 MySQL 来执行此操作
mysqld --event-scheduler=DISABLED
或者,您可以在 my.cnf 配置文件中添加一行
event_scheduler=DISABLED
如果事件调度器没有被禁用,您将能够在运行时将其打开/关闭。它由一个全局系统变量控制
SELECT event_scheduler -- values: ON / OFF / DISABLED
SET GLOBAL event_scheduler = ON
SET GLOBAL event_scheduler = OFF
如果事件调度器已打开,您可以使用 SHOW PROCESSLIST 检查其状态。它与所有其他线程一样显示。它的 `User` 是 'event_scheduler'。当它处于休眠状态时,`State` 的值为 'Waiting for next activation'。
管理事件
[edit | edit source]您可以使用 SQL 命令 CREATE EVENT、ALTER EVENT 和 DROP EVENT。
CREATE EVENT
[edit | edit source]最简单的情况。我们希望在明天执行一个 SQL 命令
CREATE EVENT `newevent`
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 DAY
DO
INSERT INTO `mydatabase`.`news` (`title`, `text`) VALUES ('Example!', 'This is not a reale news')
事件名称必须在 "EVENT" 后面指定。
如果您想创建一个仅在特定时间执行一次的任务,则需要使用 AT 子句。如果您不想指定一个绝对时间,而是希望在经过一段时间间隔后执行任务,则 "AT CURRENT_TIMESTAMP + INTERVAL ..." 是一种有用的语法。
如果您想创建一个定期任务(将在固定时间间隔内执行),则需要使用 EVERY 子句
CREATE EVENT `newevent2`
ON SCHEDULE EVERY 2 DAY
DO
OPTIMIZE TABLE `mydatabase`.`news`
您还可以指定开始时间和/或结束时间。任务将在固定时间间隔内从开始时间执行到结束时间
CREATE EVENT `newevent2`
ON SCHEDULE EVERY INTERVAL 1 DAY
DO
OPTIMIZE TABLE `mydatabase`.`news`
STARTS CURRENT_TIMESTAMP + 1 MONTH
ENDS CURRENT_TIMESTAMP + 3 MONTH
允许的时间单位是
YEAR, QUARTER, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND, YEAR_MONTH, DAY_HOUR, DAY_MINUTE, DAY_SECOND, HOUR_MINUTE, HOUR_SECOND, MINUTE_SECOND
DO 子句指定必须执行哪个语句。
如果一个任务由多个语句组成,则必须使用 BEGIN ... END 语法
delimiter |
CREATE EVENT `newevent`
ON SCHEDULE
EVERY 1 DAY
DO
BEGIN
DELETE FROM `logs`.`user` WHERE `deletion_time` < CURRENT_TIMESTAMP - 1 YEAR;
DELETE FROM `logs`.`messages` WHERE `deletion_time` < CURRENT_TIMESTAMP - 1 YEAR;
UPDATE `logs`.`activity` SET `last_cleanup` = CURRENT_TIMESTAMP;
END |
delimiter ;
如果具有相同名称的 EVENT 已经存在,您将收到来自服务器的错误。要抑制错误,您可以使用 IF NOT EXISTS 子句
CREATE EVENT `newevent2`
IF NOT EXISTS
ON SCHEDULE EVERY 2 DAY
DO
OPTIMIZE TABLE `mydatabase`.`news`
在 EVENT 过期后(当 AT 子句或 ENDS 子句中指定的的时间戳过期时),MySQL 会默认删除事件,因为它不再有用。您可能希望将其保留下来,以便将来进行 ALTER 并重新激活它,或者只是将其代码存储在某个地方。您可以使用 ON COMPLETION 子句来执行此操作
CREATE EVENT `newevent2`
ON SCHEDULE EVERY 2 DAY
ON COMPLETION PRESERVE
DO
OPTIMIZE TABLE `mydatabase`.`news`
或者,您可以明确告诉 MySQL 删除它,即使没有必要
CREATE EVENT `newevent2`
ON SCHEDULE EVERY 2 DAY
ON COMPLETION NOT PRESERVE
DO
OPTIMIZE TABLE `mydatabase`.`news`
如果您没有告诉 MySQL 在 EVENT 过期后保留它,但它在创建后立即过期(如果在 AT / ENDS 子句中指定了过去的 TIMESTAMP,则会发生这种情况),服务器会按照您的要求创建并删除它。但是,在这种情况下,它会返回 1588 警告以通知您。
您还可以指定 EVENT 是否必须启用。这可以通过指定 ENABLE、DISABLE 或 DISABLE ON SLAVES(用于在主服务器上执行事件,而不是在从服务器上复制它)来完成。默认情况下,EVENT 已启用。
CREATE EVENT `newevent2`
ON SCHEDULE EVERY 2 DAY
ON COMPLETION NOT PRESERVE
DISABLE
DO
OPTIMIZE TABLE `mydatabase`.`news`
要修改此行为,您将使用 ALTER EVENT。
您可以为 EVENT 指定一个注释。注释的字符限制为 64 个字符。注释必须是文字,而不是表达式。例如
CREATE EVENT `newevent2`
ON SCHEDULE EVERY 2 DAY
ON COMPLETION NOT PRESERVE
DISABLE
COMMENT 'let\'s optimize some tables!'
DO
OPTIMIZE TABLE `mydatabase`.`news`
您还可以指定在执行 EVENT 时必须使用哪个用户来检查权限。默认情况下,使用 CURRENT_USER。您可以明确指定它
CREATE DEFINER = CURRENT_USER
EVENT `newevent2`
ON SCHEDULE EVERY 2 DAY
DO
OPTIMIZE TABLE `mydatabase`.`news`
要指定不同的用户,您必须具有 SUPER 权限。在这种情况下,您必须同时指定用户名和主机
CREATE DEFINER = 'allen@localhost'
EVENT `newevent2`
ON SCHEDULE EVERY 2 DAY
DO
OPTIMIZE TABLE `mydatabase`.`news`
ALTER EVENT
[edit | edit source]ALTER EVENT 语句可用于修改现有 EVENT。
CREATE EVENT `newevent2`
ON SCHEDULE EVERY 2 DAY
ON COMPLETION NOT PRESERVE
RENAME TO `example_event`
DISABLE
COMMENT 'let\'s optimize some tables!'
DO
OPTIMIZE TABLE `mydatabase`.`news`
RENAME TO 用于重命名 EVENT。
您只需要指定要更改的子句
CREATE EVENT `newevent2` ENABLE;
DROP EVENT
[edit | edit source]您需要 EVENT 权限才能删除事件。要删除事件,您可以输入
DROP EVENT `event_name`
如果 EVENT 不存在,您会收到 1517 错误。要避免这种情况,您可以使用 IF EXISTS 子句
DROP EVENT IF EXISTS `event_name`
如果 EVENT 只需执行一次或只是在已知的时间段内执行,MySQL 默认会在 EVENT 过期时自动删除它(请参阅 CREATE EVENT 中的 ON COMPLETE 子句)。
元数据
[edit | edit source]SHOW CREATE EVENT
[edit | edit source]此命令返回用于创建触发器的 CREATE EVENT 语句,以及有关可能影响语句的设置的一些信息。
语法
SHOW CREATE EVENT newevent2;
- Event - 事件名称。
- sql_mode - 执行 CREATE EVENT 语句时生效的 SQL 模式。
- time_zone - 执行语句时使用的时区。
- Create Event - 用于创建事件的语句。
- character_set_client
- collation_connection
- 数据库排序规则
SHOW EVENTS
[edit | edit source]该语句显示有关当前数据库或指定数据库中 EVENT 的信息
SHOW EVENTS
SHOW EVENTS FROM `my_nice_db`
SHOW EVENTS IN `my_nice_db` -- synonym
SHOW EVENTS LIKE 'my_%' -- name starts with 'my_'
SHOW EVENTS WHERE definer LIKE 'admin@%' -- filters on any field
- Db 数据库名称。
- Name 事件名称。
- Definer 创建 EVENT 的用户以及他使用的主机,格式为 user@host。
- Time zone EVENT 使用的时区。如果从未更改,它应该为 'SYSTEM',这意味着:服务器的时区。
- Type 'ONE TIME' 表示仅执行一次的 EVENT,'RECURRING' 表示定期执行的 EVENT。
- Executed At EVENT 将执行的时刻的 TIMESTAMP。对于递归 EVENT,为 NULL。
- Interval Value EVENT 执行之间的时间间隔数。请参阅下一个字段。对于仅执行一次的 EVENT,为 NULL。
- Interval Field EVENT 执行之间等待的时间间隔类型。例如,如果 `Interval Field` 为 'SECOND' 且 `Interval Value` 为 30,则 EVENT 将每 30 秒执行一次。对于仅执行一次的 EVENT,为 NULL。
- Starts 递归 EVENT 的第一次执行 DATETIME。对于仅执行一次的事件,为 NULL。
- Ends 递归 EVENT 的最后一次执行 DATETIME。对于仅执行一次的事件,为 NULL。
- Status ENABLED、DISABLED 或 SLAVESIDE_DISABLED。对于 ENABLED 和 DISABLED,请参阅以上内容。SLAVESIDE_DISABLED 是在 5.1 中添加的,表示 EVENT 在主服务器上已启用,但在从服务器上已禁用。
- Originator 创建 EVENT 的服务器的 ID。如果它是在当前服务器上创建的,则该值为 0。在 5.1 中添加。
- character_set_client
- collation_connection
- 数据库排序规则
INFORMATION_SCHEMA.EVENTS
[edit | edit source]INFORMATION_SCHEMA 虚拟数据库有一个 `EVENTS` 表。它是非标准的,是在 5.1 中添加的。EVENTS 具有以下字段
- EVENT_CATALOG 始终为 NULL(CATALOG 在 MySQL 中未实现)。
- EVENT_SCHEMA 数据库名称。
- EVENT_NAME 事件名称。
- DEFINER 创建 EVENT 的用户以及他使用的主机,格式为 user@host。
- TIME_ZONE EVENT 使用的时区。如果从未更改,它应该为 'SYSTEM',这意味着:服务器的时区。
- EVENT_BODY 用于编写将执行的例程的语言。
- EVENT_DEFINITION 将执行的例程。
- EVENT_TYPE 'ONE TIME' 表示仅执行一次的 EVENT,'RECURRING' 表示定期执行的 EVENT。
- EXECUTE_AT EVENT 将执行的时刻的 TIMESTAMP。对于递归 EVENT,为 NULL。
- INTERVAL_VALUE EVENT 执行之间的时间间隔数。请参阅下一个字段。对于仅执行一次的 EVENT,为 NULL。
- INTERVAL_FIELD EVENT 执行之间等待的时间间隔类型。例如,如果 `Interval Field` 为 'SECOND' 且 `Interval Value` 为 30,则 EVENT 将每 30 秒执行一次。对于仅执行一次的 EVENT,为 NULL。
- SQL_MODE 创建 EVENT 时生效的 SQL 模式。
- STARTS 递归 EVENT 的第一次执行 DATETIME。对于仅执行一次的事件,为 NULL。
- ENDS 递归 EVENT 的最后一次执行 DATETIME。对于仅执行一次的事件,为 NULL。
- STATUS ENABLED、DISABLED 或 SLAVESIDE_DISABLED。对于 ENABLED 和 DISABLED,请参阅以上内容。SLAVESIDE_DISABLED 是在 5.1 中添加的,表示 EVENT 在主服务器上已启用,但在从服务器上已禁用。
- ON_COMPLETION 'NOT PRESERVE'(EVENT 将被删除)或 'PRESERVE'(EVENT 不会被删除)。
- CREATED 创建时间 DATETIME。
- LAST_ALTERED 最后一次编辑的 DATETIME。如果 EVENT 从未被修改,则 `LAST_ALTERED` 与 `CREATED` 具有相同的值。
- LAST_EXECUTED 最后一次执行的 TIMESTAMP。如果 EVENT 从未被执行,则该值为 NULL。
- EVENT_COMMENT 与 EVENT 关联的注释。如果没有注释,则该值为一个空字符串。
- ORIGINATOR 创建 EVENT 的服务器的 ID。如果它是在当前服务器上创建的,则该值为 0。在 5.1 中添加。
- character_set_client
- collation_connection
- 数据库排序规则
存储例程是使用 SQL(带有一些过程扩展)编写的模块,可以使用 CALL 命令在另一个语句中调用。
如果存储例程返回结果,则称为函数;如果它们不返回任何内容,则称为过程。存储过程不要与在 C 或 LUA 中编写的过程混淆,这些过程可以在 SELECT 语句中使用;存储函数不要与 UDF 混淆,即使它们都是使用 CREATE FUNCTION 语句创建的。
- 它们减少了网络流量:它们可能包含许多语句,但只需要发送一个语句来调用它们。
- 能够将逻辑保留在数据库中。
- 可重用模块,可以从外部程序调用,无论它们是用什么语言编写的。
- 您可以修改存储例程,而无需更改程序。
- 调用存储例程的用户无需访问它读取/写入的表。
- 调用存储例程比执行单个语句更快。
CREATE DEFINER = `root`@`localhost` PROCEDURE `Module1` ( ) NOT DETERMINISTIC NO SQL SQL SECURITY DEFINER OPTIMIZE TABLE wiki1_page;
CALL `Module1` ();
DROP PROCEDURE `Module1` ;
DROP PROCEDURE `Module1` ;
CREATE DEFINER = `root`@`localhost` PROCEDURE `Module1` ( ) NOT DETERMINISTIC NO SQL SQL SECURITY DEFINER
BEGIN
OPTIMIZE TABLE wiki1_page;
OPTIMIZE TABLE wiki1_user;
END
SHOW PROCEDURE STATUS;
SHOW CREATE PROCEDURE Module1;
虚拟数据库 INFORMATION_SCHEMA 包含一个名为 `ROUTINES` 的表,其中包含函数和过程信息。
此表包含所有存储函数的值。
MySQL 使用一个字符作为分隔符 - MySQL 知道该字符出现的位置,SQL 语句结束,可能另一个语句开始。默认情况下该字符为 ';'。当您创建一个包含多个语句的存储程序时,您只输入一个语句:CREATE 命令。但是,它在它的主体中包含多个语句,这些语句用 ';' 分隔。在这种情况下,您需要通知 MySQL ';' 不识别 CREATE 语句的结束:您需要另一个分隔符。
在下面的示例中,'|' 用作分隔符
delimiter |
CREATE EVENT myevent
ON SCHEDULE EVERY 1 DAY
DO
BEGIN
TRUNCATE `my_db`.`my_table`;
TRUNCATE `my_db`.`another_table`;
END
delimiter ;
关键字是:IF, CASE, ITERATE, LEAVE LOOP, WHILE, REPEAT
[1]。
DELIMITER $$
CREATE PROCEDURE counter()
BEGIN
DECLARE x INT;
SET x = 1;
WHILE x <= 5 DO
SET x = x + 1;
END WHILE;
SELECT x; -- 6
END$$
DELIMITER ;
DELIMITER $$
CREATE PROCEDURE counter2()
BEGIN
DECLARE x INT;
SET x = 1;
boucle1: LOOP
SET x = x + 1;
IF x > 5 THEN
LEAVE boucle1;
END IF;
END LOOP boucle1;
SELECT x; -- 6
END$$
DELIMITER ;
DELIMITER $$
CREATE PROCEDURE counter3()
BEGIN
DECLARE x INT;
SET x = 1;
REPEAT
SET x = x + 1; UNTIL x > 5
END REPEAT;
SELECT x; -- 6
END$$
DELIMITER ;
游标允许以不同的方式处理每一行,但它会大大降低查询速度。
DELIMITER $$
CREATE PROCEDURE cursor1()
BEGIN
DECLARE result varchar(100) DEFAULT "";
DECLARE c1 CURSOR FOR
SELECT page_title
FROM wiki1.wiki1_page
WHERE page_namespace = 0;
OPEN c1;
FETCH c1 INTO result;
CLOSE c1;
SELECT result;
END;$$
DELIMITER ;
它们应该在循环之前声明和打开,该循环应该以不同的方式处理每条记录。要了解表的结束,我们应该在游标之后创建一个处理程序
-- Concatenate all a table column values on a row
DELIMITER $$
CREATE PROCEDURE cursor2()
BEGIN
DECLARE result varchar(100) DEFAULT "";
DECLARE total text DEFAULT "";
DECLARE done BOOLEAN DEFAULT 0;
DECLARE c2 CURSOR FOR
SELECT page_title
FROM wiki1.wiki1_page
WHERE page_namespace = 0;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN c2;
REPEAT
FETCH c2 INTO result;
set total = concat(total, result);
UNTIL done END REPEAT;
CLOSE c2;
SELECT total;
END;$$
DELIMITER ;
处理程序声明允许在发生错误时指定处理方法[2]
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
此外,可以指示错误类型
DECLARE CONTINUE HANDLER FOR SQLSTATE [VALUE] sqlstate_value
DECLARE CONTINUE HANDLER FOR SQLWARNING
DECLARE CONTINUE HANDLER FOR NOT FOUND