20.2.1. CREATE PROCEDURE 和 CREATE FUNCTION
20.2.2. ALTER PROCEDURE 和 ALTER FUNCTION
20.2.3. DROP PROCEDURE 和和 DROP FUNCTION
20.2.4. SHOW CREATE
PROCEDURE 和 SHOW CREATE FUNCTION
MySQL
5.1 版支持存储程序和函数。一个存储程序是可以被存储在服务器中的一套SQL 语句。一旦它被存储了,客户端不需要再重新发布单独的语句,而是可以引用存储程序来替代。
下面一些情况下存储程序尤其有用:
· 当用不同语言编写多客户应用程序,或多客户应用程序在不同平台上运行且需要执行相同的数据库操作之时。
·
安全极为重要之时。比如,银行对所有普通操作使用存储程序。这提供一个坚固而安全的环境,程序可以确保每一个操作都被妥善记入日志。在这样一个设置中,应用程序和用户不可能直接访问数据库表,但是仅可以执行指定的存储程序。
存储程序可以提供改良后的性能,因为只有较少的信息需要在服务器和客户算之间传送。代价是增加数据库服务器系统的负荷,因为更多的工作在服务器这边完成,更少的在客户端 (应用程序) 那边完成上。如果许多客户端机器 (比如网页服务器) 只由一个或少数几个数据库服务器提供服务,可以考虑一下存储程序。
存储程序也允许你在数据库服务器上有函数库。这是一个被现代应用程序语言共享的特征,它允许这样的内部设计,比如通过使用类。使用这些客户端应用程序语言特征对甚至于数据库使用范围以外的编程人员都有好处。
MySQL 为存储程序遵循 SQL:2003 语法,这个语法也被用在IBM 的 DB2 数据库上。
MySQL 对存储程序的实现还在进度中。所有本章叙述的语法都被支持,在有限制或扩展的地方会恰当地指出来。有关使用
存储程序的限制的更多讨论在附录 I,
特性限制里提到。
如20.4 节,“存储子程序和触发程序的二进制日志功能”里所说的,
存储子程序的二进制日志功能已经完成。
存储程序需要在 mysql 数据库中有 proc 表。这个表在 MySQL
5.1 安装过程中创建。如果你从早期的版本升级到 MySQL 5.1 ,请确定更新你的授权表以确保 proc 表的存在。请参阅2.10.2 节 “升级授权表”。
在 MySQL 5.1 中,授权系统如下考虑存储子程序:
· 创建存储子程序需要 CREATE ROUTINE 权限。
·
提醒或移除存储子程序需要 ALTER
ROUTINE 权限。这个权限自动授予子程序的创建者。
·
执行子程序需要 EXECUTE 权限。然而,这个权限自动授予
子程序的创建者。同样,子程序默认的 SQL
SECURITY 特征是 DEFINER,它允许用该子程序访问数据库的用户与执行子程序联系到一起。
20.2.1. CREATE
PROCEDURE 和 CREATE FUNCTION
20.2.2. ALTER
PROCEDURE 和 ALTER FUNCTION
20.2.3. DROP
PROCEDURE 和 DROP FUNCTION
20.2.4. SHOW
CREATE PROCEDURE 和 SHOW CREATE FUNCTION
存储程序和函数是用 CREATE PROCEDURE 和 CREATE
FUNCTION 语句创建的子程序。一个子程序要么是一个程序要么是一个函数。使用 CALL语句来调用
程序,程序只能用输出变量传回值。就像别其它函数调用一样,函数可以被从语句外调用 (即通过引用函数名),函数能返回标量值。
存储子程序也可以调用其它存储子程序。
在 MySQL 5.1 中,一个存储子程序或函数与特定的数据库相联系。这里有几个意思:
· 当一个子程序被调用时,一个隐含的 USE db_name 被执行 (当子程序终止时停止执行)。存储子程序内的USE 语句时不允许的。
· 你可以使用数据库名限定子程序名。这可以被用来引用一个不在当前数据库中的子程序。比如,要引用一个与 test数据库关联的存储程序 p 或函数 f,你可以说 CALL test.p() 或 test.f()。
·
数据库移除的时候,与它关联的所有存储子程序也都被移除。
MySQL 支持非常有用的扩展,即它允许在存储程序中使用常规的 SELECT 语句 (那就是说,不使用光标或 局部变量)。这个一个查询的结果包被简单地直接送到客户端。多 SELECT 语句生成多个结果包,所以客户端必须使用支持多结果包的 MySQL 客户端库。这意味这客户端必须 使用至少MySQL 4.1 以来的近期版本上的客户端库。
下面一节描述用来创建,改变,移除和查询存储程序和函数的语法。
CREATE PROCEDURE sp_name ([proc_parameter[,...]])
[characteristic ...] routine_body
CREATE FUNCTION sp_name ([func_parameter[,...]])
RETURNS type
[characteristic ...] routine_body
proc_parameter:
[ IN | OUT | INOUT ] param_name type
func_parameter:
param_name type
type:
Any valid MySQL data type
characteristic:
LANGUAGE SQL
| [NOT] DETERMINISTIC
| { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }
| COMMENT 'string'
routine_body:
Valid SQL procedure statement or statements
这些语句创建存储子程序。要在MySQL
5.1 中创建子程序,必须具有CREATE
ROUTINE 权限,并且 ALTER ROUTINE 和 EXECUTE权限被自动授予它的创建者。如果二进制日志功能被允许,你也可能需要SUPER权限,请参阅20.4 节,“存储子程序和触发程序的二进制日志功能”。
默认地,子程序与当前数据库关联。要明确地把子程序与一个给定数据库关联起来,可以在创建子程序的时候指定其名字为db_name.sp_name。
如果子程序名和内建的SQL 函数名一样,定义子程序时,你需要在这个名字和随后括号中间插入一个空格,否则发生语法错误。当你随后调用子程序的时候也要插入。为此,即使有可能出现这种情况,我们还是建议最好避免给你自己的 存储子程序取与存在的 SQL 函数一样的名字。
由括号包围的参数列必须总是存在。如果没有参数,也该使用一个空参数列 ()。每个参数 默认都是一个 IN 参数。要指定为其它参数,可在参数名之前使用关键词 OUT 或 INOUT
注意:指定参数为 IN,
OUT,或 INOUT 只对 PROCEDURE 是合法的。(FUNCTION 参数总是被认为是 IN 参数)
RETURNS 字句只能对 FUNCTION做指定,对函数而言这是强制的。它用来指定函数的返回类型,而且函数体必须包含一个RETURN value 语句。
routine_body
包含合法的 SQL 过程语句。可以使用复合语句语法,请参阅20.2.7 节,“BEGIN ... END 复合语句”。复合语句可以包含
声明,循环和其它控制结构语句。这些语句的语法在本章后免介绍,举例,请参阅20.2.8 节,“DECLARE 语句”和20.2.12 节,“流程控制构造”。
CREATE FUNCTION 语句被用在更早的 MySQL 版本上以支持 UDF (自定义函数)。请参阅27.2 节,“给 MySQL 添加新函数”。 UDF 继续被支持,即使现在
有了存储函数。UDF 会被认为一个外部存储函数。然而,不要让存储函数与UDF 函数共享名字空间。
外部存储程序的框架将在不久的将来引入。这将允许你用SQL 之外的语言编写存储程序。最可能的是,第一个被支持语言是 PHP,因为核心 PHP 引擎很小,线程安全,且可以被方便地嵌入。因为框架是公开的,它希望许多其它语言也能被支持。
如果程序或线程总是对同样的输入参数产生同样的结果,则被认为它是“确定的”,否则就是“非确定”的。如果既没有给定 DETERMINISTIC 也没有给定 NOT
DETERMINISTIC,默认的就是 NOT DETERMINISTIC。
为进行复制,使用 NOW() 函数 (或它的同义词) 或
当前来讲,DETERMINISTIC 特征被接受,但还没有被优化程序所使用。然而如果二进制日志功能被允许了,这个特征影响到MySQL 是否会接受子程序定义。请参阅20.4节,“存储子程序和触发程序的二进制日志功能”。
一些特征提供子程序使用数据的内在信息。CONTAINS SQL 表示子程序不包含读或写数据的语句。NO SQL 表示子程序不包含 SQL 语句。READS
SQL DATA 表示子程序包含读数据的语句,但不包含写数据的语句。MODIFIES SQL DATA表示子程序包含写数据的语句。如果这些特征没有明确给定,默认的是 CONTAINS SQL。
SQL SECURITY特征可以用来指定
子程序该用创建子程序者的许可来执行,还是使用调用者的许可来执行。默认值是DEFINER。在 SQL:2003 中者是一个新特性。创建者或调用者必须由访问
子程序关联的数据库的许可。在 MySQL 5.1 中,必须有 EXECUTE 权限才能执行子程序。必须拥有这个权限的用户要么是定义者,要么是调用者,这取决于 SQL SECURITY 特征是如何设置的。
MySQL 存储 sql_mode 系统变量设置,这个设置在子程序被创建的时候起作用,MySQL 总是强制使用这个设置来执行
子程序。
COMMENT 子句是一个 MySQL 的扩展,它可以被用来描述
存储程序。这个信息被 SHOW CREATE PROCEDURE 和 SHOW CREATE
FUNCTION 语句来显示。
MySQL 允许子程序包含 DDL 语句,如 CREATE 和 DROP。MySQL 也允许存储程序 (但不是
存储函数) 包含 SQL 交互语句,如 COMMIT。存储函数不可以包含那些做明确的和绝对的提交或者做回滚的语。SQL 标准不要求对这些语句的支持,SQL 标准声明每个 DBMS 提供商可以决定是否允许支持这些语句。
存储子程序不能使用 LOAD DATA
INFILE。
返回结果包的语句不能被用在存储函数种。这包括不使用 INTO 给变量读取
列值的 SELECT 语句,SHOW
语句,及其它诸如 EXPLAIN 这样的语句。对于可在函数定义时间被决定要返回一个结果包的语句,发生一个允许从函数错误返回结果包的 Not(ER_SP_NO_RETSET_IN_FUNC)。对于只可在运行时决定要返回一个结果包的语句,
发生一个不能在给定上下文错误返回结果包的 PROCEDURE %s
(ER_SP_BADSELECT)。
下面是一个使用 OUT 参数的简单的存储程序的例子。例子为,在
程序被定义的时候,用mysql客户端 delimiter 命令来把语句定界符从 ;变为//。这就允许用在
程序体中的;定界符被传递到服务器而不是被mysql自己来解释。
mysql> delimiter //
mysql> CREATE PROCEDURE simpleproc (OUT param1 INT)
-> BEGIN
-> SELECT COUNT(*) INTO param1 FROM t;
-> END
-> //
Query OK, 0 rows affected (0.00 sec)
mysql> delimiter ;
mysql> CALL simpleproc(@a);
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @a;
+------+
| @a |
+------+
| 3 |
+------+
1 row in set (0.00 sec)
当使用 delimiter 命令时,你应该避免使用反斜杠 (‘\’)字符,因为那是 MySQL 的
转义字符。
下列是一个例子,一个采用参数的函数使用一个 SQL 函数执行一个操作,并返回结果:
mysql> delimiter //
mysql> CREATE FUNCTION hello (s CHAR(20)) RETURNS CHAR(50)
-> RETURN CONCAT('Hello, ',s,'!');
-> //
Query OK, 0 rows affected (0.00 sec)
mysql> delimiter ;
mysql> SELECT hello('world');
+----------------+
| hello('world') |
+----------------+
| Hello, world! |
+----------------+
1 row in set (0.00 sec)
如果在存储函数中的 RETURN 语句返回一个类型不同于在函数的RETURNS 子句中指定类型的值,返回值被强制为恰当的类型。比如,如果一个函数返回一个 ENUM 或 SET 值,但是 RETURN 语句返回一个整数,对于 SET 成员集的相应的 ENUM 成员,从函数返回的值是字符串。
ALTER {PROCEDURE | FUNCTION} sp_name [characteristic ...]
characteristic:
{ CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }
| COMMENT 'string'
这个语句可以被用来改变一个存储程序或函数的特征。在 MySQL
5.1 中,你必须用 ALTER ROUTINE 权限才可用此子程序。这个权限被自动授予子程序的创建者。如20.4 节,“存储子程序和触发程序的二进制日志功能”中所述,
如果二进制日志功能被允许了,你可能也需要 SUPER 权限。
在 ALTER PROCEDURE 和 ALTER FUNCTION 语句中,可以指定超过一个的改变。
DROP {PROCEDURE | FUNCTION} [IF EXISTS] sp_name
这个语句被用来移除一个存储程序或函数。即,从服务器移除一个制定的子程序。在 MySQL
5.1 中,你必须有 ALTER ROUTINE 权限才可用此子程序。这个权限被自动授予子程序的创建者。
IF EXISTS 子句是一个 MySQL 的扩展。如果程序或函数不存储,它防止发生错误。产生一个可以用 SHOW
WARNINGS 查看的警告。
SHOW CREATE {PROCEDURE | FUNCTION} sp_name
这个语句是一个 MySQL 的扩展。类似于 SHOW CREATE TABLE,它返回一个可用来重新创建已命名
子程序的确切字符串。
mysql> SHOW CREATE FUNCTION test.hello\G
*************************** 1. row ***************************
Function: hello
sql_mode:
Create Function: CREATE FUNCTION `test`.`hello`(s CHAR(20)) RETURNS CHAR(50)
RETURN CONCAT('Hello, ',s,'!')
SHOW {PROCEDURE | FUNCTION} STATUS [LIKE 'pattern']
这个语句是一个 MySQL 的扩展。它返回子程序的特征,如数据库,名字,类型,创建者及创建和修改日期。如果没有指定样式,根据你使用的语句,所有
存储程序和所有存储函数的信息都被列出。
mysql> SHOW FUNCTION STATUS LIKE 'hello'\G
*************************** 1. row ***************************
Db: test
Name: hello
Type: FUNCTION
Definer: testuser@localhost
Modified: 2004-08-03 15:29:37
Created: 2004-08-03 15:29:37
Security_type: DEFINER
Comment:
你可以从 INFORMATION_SCHEMA 中的 ROUTINES 表获得有关存储子程序的信息。请参阅23.1.14 节,“INFORMATION_SCHEMA
ROUTINES 表”。
CALL sp_name([parameter[,...]])
CALL 语句调用一个先前用 CREATE
PROCEDURE 创建的程序。
CALL语句可以用 声明为 OUT 或的 INOUT 参数的参数给它的调用者传回值。它也“返回”受影响的行数,客户端程序可以在SQL 级别通过调用ROW_COUNT()函数获得这个数,从 C 中是调用 the mysql_affected_rows() C API 函数来获得。
[begin_label:] BEGIN
[statement_list]
END [end_label]
存储子程序可以使用BEGIN ... END 复合语句来包含多个语句。statement_list
代表一个或多个语句的列表。statement_list之内每个语句都必须用分号 (;) 来结尾。
复合语句可以被标记。除非begin_label存在,否则end_label不能被给出,并且如果二者都存在,他们必须是同样的。
请注意,可选的 [NOT] ATOMIC 子句现在还不被支持。这意味着在指令块的开始没有交互的存储点被设置,并且在上下文中用到的 BEGIN 子句对当前交互动作没有影响。
使用多重语句需要客户端能发送包含语句定界符;的查询字符串。这个符号在命令行客户端被用 delimiter 命令来处理。改变查询结尾定界符;(比如改变为//) 使得; 可被用在子程序体中。
DECLARE 语句被用来把不同项目局域到一个
子程序:局部变量 (请参阅20.2.9 节,“存储程序中的变量”),条件和
处理程序 (请参阅20.2.10 节,“条件和处理程序”)
及光标 (请参阅20.2.11 节,“光标”)。SIGNAL 和 RESIGNAL 语句当前还不被支持。
DECLARE 仅被用在 BEGIN ... END 复合语句里,并且必须在复合语句的开头,在任何其它语句之前。
光标必须在声明处理程序之前被声明,并且变量和条件必须在声明光标或处理程序之前被声明。
你可以在子程序中声明并使用变量。
DECLARE var_name[,...] type [DEFAULT value]
这个语句被用来声明局部变量。要给变量提供一个默认值,请包含一个 DEFAULT 子句。值可以被指定为一个表达式,不需要为一个常数。如果没有 DEFAULT 子句,初始值为 NULL。
局部变量的作用范围在它被声明的 BEGIN ... END 块内。它可以被用在嵌套的块中,除了那些用相同名字
声明变量的块。
SET var_name = expr [, var_name = expr] ...
在存储程序中的 SET 语句是一般 SET 语句的扩展版本。被参考变量可能是子程序内声明的变量,或者是全局服务器变量。
在存储程序中的 SET 语句作为预先存在的 SET 语法的一部分来实现。这允许 SET a=x, b=y, ...这样的扩展语法。其中不同的变量类型 (局域 声明变量及全局和集体变量) 可以被混合起来。这也允许把局部变量和一些只对系统变量有意义的选项合并起来。在那种情况下,此选项被识别,但是被忽略了。
SELECT col_name[,...] INTO var_name[,...] table_expr
这个 SELECT 语法把选定的列直接存储到变量。因此,只有单一的行可以被取回。
SELECT id,data INTO x,y FROM test.t1 LIMIT 1;
注意,用户变量名在 MySQL 5.1 中是对大小写不敏感的。请参阅9.3 节,“用户变量”。
重要: SQL 变量名不能和列名一样。如果 SELECT ... INTO 这样的 SQL 语句包含一个对列的参考,并包含一个与列相同名字的
局部变量,MySQL 当前把参考解释为一个变量的名字。例如,在下面的语句中,xname
被解释为到 xname variable
的参考而不是到 xname column的:
CREATE PROCEDURE sp1 (x VARCHAR(5))
BEGIN
DECLARE xname VARCHAR(5) DEFAULT 'bob';
DECLARE newname VARCHAR(5);
DECLARE xid INT;
SELECT xname,id INTO newname,xid
FROM table1 WHERE xname = xname;
SELECT newname;
END;
当这个程序被调用的时候,无论 table.xname 列的值是什么,变量 newname 将返回值‘bob’。
特定条件需要特定处理。这些条件可以联系到错误,以及子程序中的一般流程控制。
DECLARE condition_name CONDITION FOR condition_value
condition_value:
SQLSTATE [VALUE] sqlstate_value
| mysql_error_code
这个语句指定需要特殊处理的条件。它将一个名字和指定的错误条件关联起来。这个名字可以随后被用在 DECLARE HANDLER 语句中。请参阅20.2.10.2 节,“DECLARE 处理程序”。
除了 SQLSTATE 值,也支持 MySQL 错误代码。
DECLARE handler_type HANDLER FOR condition_value[,...] sp_statement
handler_type:
CONTINUE
| EXIT
| UNDO
condition_value:
SQLSTATE [VALUE] sqlstate_value
| condition_name
| SQLWARNING
| NOT FOUND
| SQLEXCEPTION
| mysql_error_code
这个语句指定每个可以处理一个或多个条件的处理程序。如果产生一个或多个条件,指定的语句被执行。
对一个 CONTINUE 处理程序,当前子程序的执行在执行 处理程序语句之后继续。对于 EXIT 处理程序,当前 BEGIN...END 复合语句的执行被终止。UNDO 处理程序类型语句还不被支持。
· SQLWARNING是对所有以 01 开头的 SQLSTATE 代码的速记。
· NOT FOUND是对所有以 02 开头的 SQLSTATE 代码的速记。
· SQLEXCEPTION是对所有没有被 SQLWARNING 或 NOT FOUND 捕获的 SQLSTATE 代码的速记。
除了 SQLSTATE 值,MySQL 错误代码也不被支持。
例如:
mysql> CREATE TABLE test.t (s1 int,primary key (s1));
Query OK, 0 rows affected (0.00 sec)
mysql> delimiter //
mysql> CREATE PROCEDURE handlerdemo ()
-> BEGIN
-> DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @x2 = 1;
-> SET @x = 1;
-> INSERT INTO test.t VALUES (1);
-> SET @x = 2;
-> INSERT INTO test.t VALUES (1);
-> SET @x = 3;
-> END;
-> //
Query OK, 0 rows affected (0.00 sec)
mysql> CALL handlerdemo()//
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @x//
+------+
| @x |
+------+
| 3 |
+------+
1 row in set (0.00 sec)
注意到,@x 是 3,这表明 MySQL 被执行到程序的末尾。如果 DECLARE
CONTINUE HANDLER FOR SQLSTATE '23000' SET @x2 = 1; 这一行不在,第二个 INSERT 因 PRIMARY KEY 强制而失败之后,MySQL 可能已经采取
默认 (EXIT) 路径,并且 SELECT @x 可能已经返回 2。
简单光标在存储程序和函数内被支持。语法如同在嵌入的 SQL 中。光标当前是不敏感的,只读的及不滚动的。不敏感意为服务器可以活不可以复制它的结果表。
光标必须在声明处理程序之前被声明,并且变量和条件必须在声明光标或处理程序之前被声明。
例如:
CREATE PROCEDURE curdemo()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE a CHAR(16);
DECLARE b,c INT;
DECLARE cur1 CURSOR FOR SELECT id,data FROM test.t1;
DECLARE cur2 CURSOR FOR SELECT i FROM test.t2;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
OPEN cur1;
OPEN cur2;
REPEAT
FETCH cur1 INTO a, b;
FETCH cur2 INTO c;
IF NOT done THEN
IF b < c THEN
INSERT INTO test.t3 VALUES (a,b);
ELSE
INSERT INTO test.t3 VALUES (a,c);
END IF;
END IF;
UNTIL done END REPEAT;
CLOSE cur1;
CLOSE cur2;
END
DECLARE cursor_name CURSOR FOR select_statement
这个语句声明一个光标。也可以在子程序中定义多个光标,但是一个块中的每一个光标必须有唯一的名字。
SELECT 语句不能有 INTO 子句。
IF, CASE, LOOP,
WHILE, ITERATE,及 LEAVE 构造被完全实现。
这些构造可能每个包含要么一个单独语句,要么是使用 BEGIN ... END 复合语句的一块语句。构造可以被嵌套。
目前还不支持 FOR 循环。
IF search_condition THEN statement_list
[ELSEIF search_condition THEN statement_list] ...
[ELSE statement_list]
END IF
IF实现了一个基本的条件构造。如果search_condition求值为真,相应的 SQL 语句列表被执行。如果没有search_condition匹配,在 ELSE 子句里的语句列表被执行。statement_list可以包括一个或多个语句。
CASE case_value
WHEN when_value THEN statement_list
[WHEN when_value THEN statement_list] ...
[ELSE statement_list]
END CASE
Or:
CASE
WHEN search_condition THEN statement_list
[WHEN search_condition THEN statement_list] ...
[ELSE statement_list]
END CASE
存储程序的 CASE 语句实现一个复杂的条件构造。如果search_condition 求值为真,相应的 SQL 被执行。如果没有搜索条件匹配,在 ELSE 子句里的语句被执行。
注意:这里介绍的用在
存储程序里的 CASE 语句与12.2 节,“控制流程函数”里描述的 SQL CASE 表达式的 CASE 语句有轻微不同。这里的 CASE 语句不能有 ELSE
NULL 子句,并且用 END CASE 替代 END 来终止。
[begin_label:] LOOP
statement_list
END LOOP [end_label]
LOOP允许某特定语句或语句群的重复执行,实现一个简单的循环构造。在循环内的语句一直重复直循环被退出,退出通常伴随着一个LEAVE 语句。
LOOP 语句可以被标注。除非begin_label存在,否则end_label不能被给出,并且如果两者都出现,它们必须是同样的。
ITERATE label
ITERATE 只可以出现在 LOOP, REPEAT,
和 WHILE 语句内。ITERATE 意思为:&ldquo;再次循环。”
例如:
CREATE PROCEDURE doiterate(p1 INT)
BEGIN
label1: LOOP
SET p1 = p1 + 1;
IF p1 < 10 THEN ITERATE label1; END IF;
LEAVE label1;
END LOOP label1;
SET @x = p1;
END
[begin_label:] REPEAT
statement_list
UNTIL search_condition
END REPEAT [end_label]
REPEAT 语句内的语句或语句群被重复,直至search_condition 为真。
REPEAT
语句可以被标注。 除非begin_label也存在,end_label才能被用,如果两者都存在,它们必须是一样的。
例如:
mysql> delimiter //
mysql> CREATE PROCEDURE dorepeat(p1 INT)
-> BEGIN
-> SET @x = 0;
-> REPEAT SET @x = @x + 1; UNTIL @x > p1 END REPEAT;
-> END
-> //
Query OK, 0 rows affected (0.00 sec)
mysql> CALL dorepeat(1000)//
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @x//
+------+
| @x |
+------+
| 1001 |
+------+
1 row in set (0.00 sec)
是的,在存储程序和函数中被执行标准行为被从主 MySQL 服务器复制到从服务器。有少数限制,它们在20.4 节,“存储子程序和 触发程序二进制日志功能”中详述。
是的,通过一般 DDL 语句执行的存储程序和函数,其在主服务器上的创建被复制到从服务器,所以目标将存在两个服务器上。对存储程序和函数的 ALTER 和 DROP 语句也被复制。
MySQL 纪录每个发生在存储程序和函数里的 DML 事件,并复制这些单独的行为到从服务器。执行存储程序和函数的切实调用不被复制。
是的,因为一个从服务器有权限来执行任何读自主服务器的二进制日志的语句,指定的安全约束因与复制一起使用的存储程序和函数而存在。如果复制或二进制日志大体上是激活的 (为 point-in-time 恢复的目的),那么 MySQL DBA 有两个安全选项可选:
嵌入到存储程序中的不确定 (随机) 或时基行不能适当地复制。随机产生的结果,仅因其本性,是你可预测的和不能被确实克隆的。因此,复制到从服务器的随机行为将不会镜像那些产生在主服务器上的。注意, 声明存储程序或函数为 DETERMINISTIC 或者在 log_bin_trust_routine_creators 中设置系统变量为 0 将会允许随即值操作被调用。
此外,时基行为不能在从服务器上重新产生,因为在存储程序中通过对复制使用的二进制日志来计时这样的时基行为是不可重新产生的,因为该二进制日志仅纪录 DML 事件且不包括计时约束。
最后,在大型 DML 行为 (如大批插入) 中非交互表发生错误,该非交互表可能经历复制,在复制版的非交互表中主服务器可以被部分地从 DML 行为更新。但是因为发生的那个错误,对从服务器没有更新。 对函数的 DML 行为,工作区将被用 IGNORE 关键词来执行,以便于在主服务器上导致错误的更新被忽略,并且不会导致错误的更新被复制到从服务器。
影响复制的同一限制会影响 point-in-time 恢复。
将来发行的 MySQL 预期有一个功能去选择复制该如何被处理:
MySQL 5.1 中的触发程序和复制象在大多数其它数据库引擎中一样工作,在那些引擎中,通过触发程序在主服务器上执行的行为不被复制到从服务器。取而代之的是,位于主 MySQL 服务器的表中的 触发程序需要在那些存在于任何 MySQL 从服务器上的表内被创建,以便于触发程序可以也可以在从服务器上被激活。
首先,主服务器上的触发程序必须在从服务器上重建。一旦重建了,复制流程就象其它参与到复制中的标准 DML 语句一样工作。例如:考虑一个已经插入触发程序 AFTER 的 EMP 表,它位于主 MySQL 服务器上。同样的 EMP 表和 AFTER 插入 触发程序也存在于从服务器上。复制流程可能是:
1.
对 EMP 做一个 INSERT 语句。
2. EMP 上的 AFTER 触发程序激活。
3. INSERT 语句被写进二进制日志。
4.
从服务器上的复制拾起 INSERT 语句给 EMP 表,并在从服务器上执行它。
5. 位于从服务器 EMP上的AFTER 触发程序激活。
,这一节介绍 MySQL 5.1 如何考虑二进制日志功能来处理存储子程序 (程序和函数)
。这一节也适用于触发程序。
二进制日志包含修改数据库内容的 SQL 语句的信息。这个信息以描述修改的事件的形式保存起来。
二进制日志有两个重要目的:
·
复制的基础是主服务器发送包含在二进制日志里的事件到从服务器,从服务器执行这些事件来造成与对主服务器造成的同样的数据改变,请参阅6.2 节,“复制概述”。
·
特定的数据恢复操作许要使用二进制日志。备份的文件被恢复之后,备份后纪录的二进制日志里的事件被重新执行。这些事件把数据库带从备份点的日子带到当前。请参阅5.9.2.2 节,“使用备份恢复”。
MySQL 中,以存储子程序的二进制日志功能引发了很多问题,这些在下面讨论中列出,作为参考信息。
除了要另外注意的之外,这些谈论假设你已经通过用--log-bin 选项启动服务器允许了二进制日志功能。(如果二进制日志功能不被允许,复制将不可能,为数据恢复的二进制日志也不存在。) 请参阅5.11.3 节,“二进制日志”。
对存储子程序语句的二进制日志功能的特征在下面列表中描述。一些条目指出你应该注意到的问题。但是在一些情况下,有你可以更改的妇五七设置或你可以用来处理它们的工作区。
·
CREATE
PROCEDURE, CREATE FUNCTION, ALTER PROCEDURE,和 ALTER FUNCTION
语句被写进二进制日志,CALL, DROP PROCEDURE,
和 DROP FUNCTION 也一样。
尽管如此,对复制有一个安全暗示:要创建一个子程序,用户必须有 CREATE ROUTINE 权限,但有这个权限的用户不能写一个
子程序在从服务器上执行任何操作。因为在从服务器上的 SQL 线程用完全权限来运行。例如,如果主服务器和从服务器分别有服务器 ID 值 1 和 2,在主服务器上的用户可能创建并调用如下一个
程序:
mysql> delimiter //
mysql> CREATE PROCEDURE mysp ()
-> BEGIN
-> IF @@server_id=2 THEN DROP DATABASE accounting; END IF;
-> END;
-> //
mysql> delimiter ;
mysql> CALL mysp();
CREATE PROCEDURE 和 CALL 语句将被写进二进制日志,所以从服务器将执行它们。因为从 SQL 线程有完全权限,它将移除 accounting 数据库。
要使允许二进制日志功能的服务器避免这个危险,MySQL 5.1 已经要求 存储程序和函数的创建者除了通常需要的 CREATE ROUTINE 的权限外,还必须有 SUPER 权限。类似地,要使用 ALTER PROCEDURE 或 ALTER FUNCTION,除了 ALTER ROUTINE 权限外你必须有 SUPER 权限。没有 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_routine_creators variable)
你可能不想强制要求子程序创建者必须有 SUPER 权限。例如,你系统上所有有 CREATE ROUTINE 权限的用户可能是有经验的应用程序开发者。要禁止掉对 SUPER 权限的要求,设置 log_bin_trust_routine_creators 全局系统变量为 1。默认地,这个变量值为 0,但你可以象这样改变这样:
mysql> SET GLOBAL log_bin_trust_routine_creators = 1;
你也可以在启动服务器之时用--log-bin-trust-routine-creators 选项来设置允许这个变量。
如果二进制日志功能不被允许,log_bin_trust_routine_creators
没有被用上,子程序创建需要 SUPER 权限。
·
一个执行更新的非确定子程序是不可重复的,它能有两个不如意的影响:
o 它会使得从服务器不同于主服务器。
- 恢复的数据与原始数据不同。
要解决这些问题,MySQL 强制做下面要求:在主服务器上,除非子程序被声明为确定性的或者不更改数据,否则创建或者替换子程序将被拒绝。这意味着当你创建一个子程序的时候,你必须要么声明它是确定性的,要么它不改变数据。两套子程序特征在这里适用:
-
DETERMINISTIC和 NOT
DETERMINISTIC 指出一个子程序是否对给定的输入总是产生同样的结果。如果没有给定任一特征,默认是 NOT DETERMINISTIC,所以你必须明确指定 DETERMINISTIC 来
声明一个
子程序是确定性的。
使用 NOW()
函数 (或它的同义) 或者 RAND()
函数不是必要地使也一个子程序非确定性。对 NOW() 而言,二进制日志包括时间戳并正确复制。RAND() 只要在一个
子程序内被调用一次也可以正确复制。(你可以认为子程序执行时间戳和随机数种子作为毫无疑问地输入,它们在主服务器和从服务器上是一样的。)
-
CONTAINS SQL, NO
SQL, READS SQL DATA,和 MODIFIES SQL 数据提供子程序是读还是写数据的信息。无论 NO
SQL 还是 READS SQL DATA i 都指出,子程序没有改变数据,但你必须明白地指明这些中的一个,因为如果任何这些特征没有被给出,
默认的特征是 CONTAINS SQL。
默认地,要一个 CREATE PROCEDURE 或 CREATE FUNCTION 语句被接受,DETERMINISTIC 或 NO SQL 与 READS SQL DATA 中的一个必须明白地指定,否则会产生如下错误:
ERROR 1418 (HY000): This routine 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_routine_creators
variable)
如果设置 log_bin_trust_routine_creators
为 1,移除对子程序必须是确定的或不修改数据的要求。
注意,子程序本性的评估是基于创建者的&ldquo;诚实度”
:MySQL 不检查声明为确定性的子程序是否不含产生非确定性结果的语句。
· 如果子程序返回无错,CALL 语句被写进二进制日志,否则就不写。当一个子程序修改数据失败了,你会得到这样的警告:
· ERROR 1417 (HY000): A routine failed and has neither NO SQL nor
· READS SQL DATA in its declaration and binary logging is enabled; if
· non-transactional tables were updated, the binary log will miss their
· changes
这个记日志行为潜在地导致问题。如果一个子程序部分地修改一个非交互表 (比如一个 MyISAM 表 able) 并且返回一个错误,二进制日志将反映这些变化。要防止这种情况,你应该在
子程序中使用交互表并且在交互动作内修改表。
在一个子程序内,如果你在 INSERT, DELETE, 或者 UPDATE 里使用 IGNORE 关键词来忽略错误,可能发生一个部分更新,但没有错误产生。这样的语句被记录日志,且正常复制。
· 如果一个存储函数在一个如 SELECT 这样不修改数据的语句内被调用,即使函数本身更改数据,函数的执行也将不被写进二进制日志里。这个记录日志的行为潜在地导致问题。假设函数 myfunc() 如下定义:
· CREATE FUNCTION myfunc () RETURNS INT
· BEGIN
· INSERT INTO t (i) VALUES(1);
· RETURN 0;
· END;
按照上面定义,下面的语句修改表 t,因为 myfunc() 修改表 t,
但是语句不被写进二进制日志,因为它是一个 SELECT 语句:
SELECT myfunc();
对这个问题的工作区将调用在做更新的语句里做更新的函数。注意,虽然 DO 语句有时为了其估算表达式的副效应而被执行,DO 在这里不是一个工作区,因为它不被写进二进制日志。
·
在一个子程序内执行的语句不被写进二进制日志。假如你发布下列语句:
· CREATE PROCEDURE mysp INSERT INTO t VALUES(1);
· CALL mysp;
对于这个例子来说,CREATE
PROCEDURE 和 CALL 语句出现在二进制日志里,但 INSERT 语句并未出现。
·
在从服务器上,当决定复制哪个来自主服务器的事件时,下列限制被应用:--replicate-*-table 规则不适用于 CALL 语句或子程序内的语句:在这些情况下,总是返回“复制!”
触发程序类似于存储函数,所以前述的评论也适用于触发程序,除了下列情况: CREATE
TRIGGER 没有可选的 DETERMINISTIC 特征,所以触发程序被假定为总是确定性的。然而,这个假设在一些情况下是非法的。比如,UUID() 函数是非确定性的 (不能复制)。你应该小心在
触发程序中使用这个函数。
触发程序目前不能更新表,但是在将来会支持。因为这个原因,如果你没有 SUPER 权限且 log_bin_trust_routine_creators 被设为 0,得到的错误信息类似于存储子程序与 CREATE TRIGGER 产生的错误信息。
在本节中叙述的问题来自发生在 SQL 语句级别的二进制日志记录的事实。未来发行的 MySQL 期望能实现行级的二进制日志记录,记录发生在更
细致的级别并且指出哪个改变作为执行 SQL 的结果对单个记录而做。
这是 MySQL 参考手册的翻译版本,关于 MySQL 参考手册,请访问dev.mysql.com。原始参考手册为英文版,与英文版参考手册相比,本翻译版可能不是最新的。