存储过程是一种存储在数据库中的程序,就像是正规语言中的子程序。我们常用的操作数据库语言 SQL 语句在执行的时候需要要先编译,然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的 SQL 语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储过程带有参数的话)来调用执行它。
MySQL 支持的“ routines(例程)”有两种:一种是我们说的存储过程, 另外一种是在其他 SQL 语句中可以返回值的函数,使用起来和 MySQL 预装载的函数一样,如 pi()。一个存储过程包括名字,参数列表,以及可以包括很多 SQL 语句的 SQL 语句集。
那么什么情况下需要用到存储过程呢?当你需要处理的是需要检查、循环、多语句但没有用户交互的重复性任务时,你就可以使用保存在服务器上的存储过程来完成。这样在执行任务的每一步时,服务器和客户端之间就没那么多的信息来往了。
想要在安装的 MySQL 上测试存储过程,必须得保证安装的 MySQL 版本是 5.0 以上,因为 5.0 之前的 MySQL版本不支持存储过程的功能,由于其它数据库都有存储过程,这使得 5.0 之前的 MySQL 版本的使用大打折扣。因此,自 MySQL 5.0 之后的版本加入了存储过程这一功能。所以在学习存储过程之前,先检查一下 MySQL 的版本。
- mysql> select version();
- +-----------+
- | version() |
- +-----------+
- | 5.7.4-m14 |
- +-----------+
- 1 row in set (0.00 sec)
或者用下面的语句也可以查询版本:
- mysql> show variables like 'version';
- +---------------+-----------+
- | Variable_name | Value |
- +---------------+-----------+
- | version | 5.7.4-m14 |
- +---------------+-----------+
- 1 row in set (0.00 sec)
注意:MySQL 语句对大小写不敏感,因此语句也可以是大写的,比如 SELECT VERSION();
创建一个测试数据库 test,用来学习存储过程。
- mysql> create database test;
- Query OK, 1 row affected (0.00 sec)
- mysql> use test;
- Database changed
在 test 数据库中,创建一张表 t,并插入一条简单的数据。
- mysql> create table t (s1 int);
- Query OK, 0 rows affected (0.05 sec)
- mysql> insert into t values (5);
- Query OK, 1 row affected (0.00 sec)
接下来,选择分隔符。分隔符是通知 mysql 客户端,已经完成输入一个 SQL 语句的字符或字符串符号。一直以来,我们都使用分号“; ”,但在存储过程中,这会产生不少问题,因为存储过程中有许多语句,所以每一个都需要一个分号。因此需要选择一个不太可能出现在 SQL 语句或程序中的字符串作为分隔符。比如“|”、“@”、“//”等。在这里就使用“//”。设置分隔符的 SQL 语句如下:
mysql> delimiter //
下面,关键的时刻来了,创建第一个存储过程吧。
- mysql> create procedure p1 () select * from t;//
- Query OK, 0 rows affected (0.00 sec)
解释上面的存储过程:
创建存储过程的关键字是 create procedure,存储过程名是 p1(备注:存储过程名就像 SQL 语句一样,对大小写不敏感,因此‘P1’和‘p1’是同一个名字,在同一个数据库中不能给两个存储过程取相同的名字,因为这样将会导致重载。某些DBMS允许重载(Oracle支持),但是MySQL不支持。 可以采取“数据库名.存储过程名”这样的折中方法,如“ test.p1”。存储过程名可以分开, 它可以包括空格符,其长度限制为 64 个字符,但注意不要使用 MySQL 内建函数的名字。),语句中 p1 后面的括号是参数列表,没有参数的情况下括号需保留。括号后到“//”前,是存储过程的主体。
接下来就调用上面创建的存储过程。
- mysql> call p1 ()//
- +------+
- | s1 |
- +------+
- | 5 |
- +------+
- 1 row in set (0.00 sec)
- Query OK, 0 rows affected (0.00 sec)
反映存储过程特性的子句——特征子句。
- mysql> create procedure p2()
- -> LANGUAGE SQL /* 说明下面过程的主体使用SQL语言编写 */
- -> NOT DETERMINISTIC /* 传递给系统的信息 */
- -> SQL SECURITY DEFINER /* 详细解释继续向下看 */
- -> COMMENT 'A Procedure' /* 可选的注释说明 */
- -> select CURRENT_DATE, RAND() from t//
- Query OK, 0 rows affected (0.00 sec)
- mysql> call p2()//
- +--------------+---------------------+
- | CURRENT_DATE | RAND() |
- +--------------+---------------------+
- | 2015-01-31 | 0.19683208334895977 |
- +--------------+---------------------+
- 1 row in set (0.00 sec)
- Query OK, 0 rows affected (0.00 sec)
SQL SECURITY,可以定义为 SQL SECURITY DEFINER 或 SQL SECURITY INVOKER。 这就进入了权限控制的领域了。SQL SECURITY DEFINER 意味着在调用时检查创建过程用户的权限,SQL SECURITY INVOKER 意味着一直会检查用户的权限。使用 SQL SECURITY DEFINER 指令告诉 MySQL 服务器检查创建过程的用户就可以了,当过程已经被调用,就不检查执行调用过程的用户了。而SQL SECURITY INVOKER 则是告诉服务器在这一步仍然要检查调用者的权限。
现在,详细解释一下存储过程的参数。
- 1. CREATE PROCEDURE p() ...
- 2. CREATE PROCEDURE p([IN] name data-type) ...
- 3. CREATE PROCEDURE p(OUT name data-type) ...
- 4. CREATE PROCEDURE p(INOUT name data-type) ...
上面的参数中,IN 是可以省略的,因为存储过程如果有参数,默认为 IN。IN 输入参数,表示该参数的值必须在调用存储过程时指定,在存储过程中修改该参数的值不能被返回,还是为原来的值;OUT 输出参数,该值可以在存储过程内部被改变,并可返回;INOUT 输入输出参数,该值在调用时指定,在存储过程内部被改变后,可以返回,为改变后的值。下面看几个通俗易懂的详细例子。
IN 输入的例子:
- mysql> create procedure demo_in_parameter(IN p_in int)
- -> begin
- -> select p_in;
- -> set p_in=2;
- -> select p_in;
- -> end //
- Query OK, 0 rows affected (0.00 sec)
- mysql> set @p_in=1 //
- Query OK, 0 rows affected (0.00 sec)
- mysql> call demo_in_parameter(@p_in) //
- +------+
- | p_in |
- +------+
- | 1 |
- +------+
- 1 row in set (0.00 sec)
- +------+
- | p_in |
- +------+
- | 2 |
- +------+
- 1 row in set (0.00 sec)
- Query OK, 0 rows affected (0.00 sec)
- mysql> select @p_in //
- +--------+
- | @p_in |
- +--------+
- | 1 |
- +--------+
- 1 row in set (0.00 sec)
OUT 输出的例子:
- mysql> create procedure demo_out_parameter(OUT p_out int)
- -> begin
- -> select p_out;
- -> set p_out=2;
- -> select p_out;
- -> end //
- Query OK, 0 rows affected (0.00 sec)
- mysql> set @p_out=1 //
- Query OK, 0 rows affected (0.00 sec)
- mysql> call demo_out_parameter(@p_out) //
- +-------+
- | p_out |
- +-------+
- | NULL |
- +-------+
- 1 row in set (0.00 sec)
- +-------+
- | p_out |
- +-------+
- | 2 |
- +-------+
- 1 row in set (0.00 sec)
- Query OK, 0 rows affected (0.00 sec)
- w
- mysql> select @p_out //
- +----------+
- | @p_out |
- +----------+
- | 2 |
- +----------+
- 1 row in set (0.00 sec)
INOUT 输入输出的例子:
- mysql> create procedure demo_inout_parameter(INOUT p_inout int)
- -> begin
- -> select p_inout;
- -> set p_inout=2;
- -> select p_inout;
- -> end //
- Query OK, 0 rows affected (0.00 sec)
- mysql> set @p_inout=1 //
- Query OK, 0 rows affected (0.00 sec)
- mysql> call demo_inout_parameter(@p_inout) //
- +---------+
- | p_inout |
- +---------+
- | 1 |
- +---------+
- 1 row in set (0.00 sec)
- +---------+
- | p_inout |
- +---------+
- | 2 |
- +---------+
- 1 row in set (0.00 sec)
- Query OK, 0 rows affected (0.00 sec)
- mysql> select @p_inout //
- +----------+
- | @p_inout |
- +----------+
- | 2 |
- +----------+
- 1 row in set (0.00 sec)
在这里,顺便说一下 MySQL 存储过程的注释方式,主要有三种:
-- 需要注释的部分:用于单行注释;
# 需要注释的部分:用于单行注释;
/* 需要注释的部分 */:用户多行注释;
注意:不管单行和多行的注释,如果把分隔符“//”写到注释中,则分隔符失效,需要在注释外重新写分隔符。另外,“--”和“#”的单行注释风格和 C 或 JAVA 等语言的“//”注释一样,不过最好在标记和被注释的内容间加个空格。
MySQL 存储过程中变量的声明和赋值:
用关键字 declare 声明变量,用 set 关键字给变量赋值。
MySQL 存储过程的控制语句:
1. 作用域问题。
- mysql> create procedure p11()
- -> begin
- -> declare xx char(5) default 'outer';
- -> begin
- -> declare xx char(5) default 'inner';
- -> select xx;
- -> end;
- -> select xx;
- -> end;
- -> //
- Query OK, 0 rows affected (0.00 sec)
- mysql> call p11() //
- +-------+
- | xx |
- +-------+
- | inner |
- +-------+
- 1 row in set (0.00 sec)
- +-------+
- | xx |
- +-------+
- | outer |
- +-------+
- 1 row in set (0.00 sec)
- Query OK, 0 rows affected (0.00 sec)
2. 条件语句 IF...THEN... 和 IF...THEN... ELSE...
- mysql> select * from t //
- +------+
- | s1 |
- +------+
- | 5 |
- | 5 |
- | 5 |
- | 5 |
- +------+
- 4 rows in set (0.00 sec)
- mysql> create procedure p12(IN pp int)
- -> begin
- -> declare vv int;
- -> set vv=pp+1;
- -> if vv=0 then
- -> insert into t values (17);
- -> end if;
- -> if pp=0 then
- -> update t set s1=s1+1;
- -> else
- -> update t set s1=s1+2;
- -> end if;
- -> end;
- -> //
- Query OK, 0 rows affected (0.00 sec)
- mysql> select * from t //
- +------+
- | s1 |
- +------+
- | 6 |
- | 6 |
- | 6 |
- | 6 |
- +------+
- 4 rows in set (0.00 sec)
3. case语句
- mysql> select * from t;//
- +------+
- | s1 |
- +------+
- | 6 |
- | 6 |
- | 6 |
- | 6 |
- +------+
- 4 rows in set (0.00 sec)
- mysql> create procedure p13(IN pp int)
- -> begin
- -> declare vv int;
- -> set vv = pp + 1;
- -> case vv
- -> when 0 then insert into t values(17);
- -> when 1 then insert into t values(18);
- -> else insert into t values (19);
- -> end case;
- -> end;
- -> //
- Query OK, 0 rows affected (0.00 sec)
- mysql> call p13(0)//
- Query OK, 1 row affected (0.01 sec)
- mysql> select * from t//
- +------+
- | s1 |
- +------+
- | 6 |
- | 6 |
- | 6 |
- | 6 |
- | 18 |
- +------+
- 5 rows in set (0.00 sec)
4. 循环语句 while...end while、loop...end loop、repeat...end repeat 以及很难驾驭的 goto。
- mysql> select * from t//
- +------+
- | s1 |
- +------+
- | 6 |
- | 6 |
- | 6 |
- | 6 |
- | 18 |
- +------+
- 5 rows in set (0.00 sec)
- mysql> create procedure p14()
- -> begin
- -> declare v int;
- -> set v = 0;
- -> while v < 5 do
- -> insert into t values(v);
- -> set v = v + 1;
- -> end while;
- -> end; //
- Query OK, 0 rows affected (0.00 sec)
- mysql> call p14() //
- Query OK, 1 row affected (0.04 sec)
- mysql> select * from t; //
- +------+
- | s1 |
- +------+
- | 6 |
- | 6 |
- | 6 |
- | 6 |
- | 18 |
- | 0 |
- | 1 |
- | 2 |
- | 3 |
- | 4 |
- +------+
- 10 rows in set (0.00 sec)
- mysql> create procedure p15()
- -> begin
- -> declare v int;
- -> set v=0;
- -> repeat
- -> insert into t values(v);
- -> set v=v+1;
- -> until v>=5
- -> end repeat;
- -> end; //
- Query OK, 0 rows affected (0.00 sec)
- mysql> create procedure p16()
- -> begin
- -> declare v int;
- -> set v=0;
- -> loop_label:LOOP
- -> insert into t values(v);
- -> set v=v+1;
- -> if v>=5 then
- -> leave loop_label;
- -> end if;
- -> end LOOP;
- -> end; //
- Query OK, 0 rows affected (0.00 sec)
以上循环的代码都会向表 t 中插入 5 行数据,值分别为 0、1、2、3、4。
下面是个迭代语句 ITERATE,相当于其它高级语言中的 continue 关键字,跳过当次循环,接着进行下一次。
- mysql> create procedure p20()
- -> begin
- -> declare v int;
- -> set v=0;
- -> loop_label:LOOP
- -> if v=3 then
- -> set v=v+1;
- -> iterate loop_label;
- -> end if;
- -> insert into t values(v);
- -> set v=v+1;
- -> if v>=5 then
- -> leave loop_label;
- -> end if;
- -> end LOOP;
- -> end; //
- Query OK, 0 rows affected (0.00 sec)
这个存储过程执行后的结果是,向表 t 中插入 4 条数据,分别为 0、1、2、4。
GOTO 循环平时很少用,原因是控制不好,可能会导致代码流程特别乱,很难管理,这里也就不多做介绍了。到这里,存储过程基础大概讲的差不多了,让我们把分隔符先还原成默认的分号。
mysql> delimiter ;
讲了这么多存储过程的东西,现在已经建了很多个存储过程了。在 MySQL 中如何管理这些存储过程呢?接下来就讲讲这方面的东西吧。
1. 查看数据库下都有什么存储过程。
语句一:
- mysql> select name from mysql.proc where db='test';
- +----------------------+
- | name |
- +----------------------+
- | demo_inout_parameter |
- | demo_in_parameter |
- | demo_out_parameter |
- | p1 |
- | p10 |
- | p11 |
- | p12 |
- | p13 |
- | p14 |
- | p15 |
- | p16 |
- | p2 |
- | p20 |
- | p7 |
- | p8 |
- | p_in |
- | p_out |
- +----------------------+
- 17 rows in set (0.00 sec)
语句二:
- mysql> select routine_name from information_schema.routines where routine_schema='test';
- +----------------------+
- | routine_name |
- +----------------------+
- | demo_inout_parameter |
- | demo_in_parameter |
- | demo_out_parameter |
- | p1 |
- | p10 |
- | p11 |
- | p12 |
- | p13 |
- | p14 |
- | p15 |
- | p16 |
- | p2 |
- | p20 |
- | p7 |
- | p8 |
- | p_in |
- | p_out |
- +----------------------+
- 17 rows in set (0.00 sec)
语句三:
- mysql> show procedure status where db='test';
- +------+----------------------+-----------+----------------+---------------------+---------------------+---------------+-------------+----------------------+----------------------+--------------------+
- | Db | Name | Type | Definer | Modified | Created | Security_type | Comment | character_set_client | collation_connection | Database Collation |
- +------+----------------------+-----------+----------------+---------------------+---------------------+---------------+-------------+----------------------+----------------------+--------------------+
- | test | demo_inout_parameter | PROCEDURE | root@localhost | 2015-02-01 14:16:36 | 2015-02-01 14:16:36 | DEFINER | | utf8 | utf8_general_ci | latin1_swedish_ci |
- | test | demo_in_parameter | PROCEDURE | root@localhost | 2015-02-01 13:56:44 | 2015-02-01 13:56:44 | DEFINER | | utf8 | utf8_general_ci | latin1_swedish_ci |
- | test | demo_out_parameter | PROCEDURE | root@localhost | 2015-02-01 14:03:09 | 2015-02-01 14:03:09 | DEFINER | | utf8 | utf8_general_ci | latin1_swedish_ci |
- | test | p1 | PROCEDURE | root@localhost | 2015-01-31 18:40:37 | 2015-01-31 18:40:37 | DEFINER | | utf8 | utf8_general_ci | latin1_swedish_ci |
- | test | p10 | PROCEDURE | root@localhost | 2015-02-01 15:06:28 | 2015-02-01 15:06:28 | DEFINER | | utf8 | utf8_general_ci | latin1_swedish_ci |
- | test | p11 | PROCEDURE | root@localhost | 2015-02-01 15:09:19 | 2015-02-01 15:09:19 | DEFINER | | utf8 | utf8_general_ci | latin1_swedish_ci |
- | test | p12 | PROCEDURE | root@localhost | 2015-02-01 15:18:15 | 2015-02-01 15:18:15 | DEFINER | | utf8 | utf8_general_ci | latin1_swedish_ci |
- | test | p13 | PROCEDURE | root@localhost | 2015-02-01 16:07:37 | 2015-02-01 16:07:37 | DEFINER | | utf8 | utf8_general_ci | latin1_swedish_ci |
- | test | p14 | PROCEDURE | root@localhost | 2015-02-01 16:16:24 | 2015-02-01 16:16:24 | DEFINER | | utf8 | utf8_general_ci | latin1_swedish_ci |
- | test | p15 | PROCEDURE | root@localhost | 2015-02-01 17:13:16 | 2015-02-01 17:13:16 | DEFINER | | utf8 | utf8_general_ci | latin1_swedish_ci |
- | test | p16 | PROCEDURE | root@localhost | 2015-02-01 17:17:10 | 2015-02-01 17:17:10 | DEFINER | | utf8 | utf8_general_ci | latin1_swedish_ci |
- | test | p2 | PROCEDURE | root@localhost | 2015-01-31 19:12:49 | 2015-01-31 19:12:49 | DEFINER | A Procedure | utf8 | utf8_general_ci | latin1_swedish_ci |
- | test | p20 | PROCEDURE | root@localhost | 2015-02-01 17:28:21 | 2015-02-01 17:28:21 | DEFINER | | utf8 | utf8_general_ci | latin1_swedish_ci |
- | test | p7 | PROCEDURE | root@localhost | 2015-02-01 14:58:04 | 2015-02-01 14:58:04 | DEFINER | | utf8 | utf8_general_ci | latin1_swedish_ci |
- | test | p8 | PROCEDURE | root@localhost | 2015-02-01 15:04:12 | 2015-02-01 15:04:12 | DEFINER | | utf8 | utf8_general_ci | latin1_swedish_ci |
- | test | p_in | PROCEDURE | root@localhost | 2015-01-31 19:47:34 | 2015-01-31 19:47:34 | DEFINER | | utf8 | utf8_general_ci | latin1_swedish_ci |
- | test | p_out | PROCEDURE | root@localhost | 2015-01-31 19:50:40 | 2015-01-31 19:50:40 | DEFINER | | utf8 | utf8_general_ci | latin1_swedish_ci |
- +------+----------------------+-----------+----------------+---------------------+---------------------+---------------+-------------+----------------------+----------------------+--------------------+
- 17 rows in set (0.00 sec)
如果想查看某一个存储过程的详细信息,可以用语句 show create procedure 数据库名.存储过程名;
mysql> show create procedure test.p2;
存储过程的修改语句 alert procedure。不过修改好像只能修改执行权限等,如果需要修改主题,很多人建议删了重建。
删除存储过程的语句是 delete procedure 存储过程名;
最后,有网友整理了些 MySQL 存储过程中的基本函数,顺便分享给大家。
- /**
- * 字符串类
- */
- CHARSET(str) //返回字串字符集
- CONCAT (string2 [,... ]) //连接字串
- INSTR (string ,substring ) //返回substring首次在string中出现的位置,不存在返回0
- LCASE (string2 ) //转换成小写
- LEFT (string2 ,length ) //从string2中的左边起取length个字符
- LENGTH (string ) //string长度
- LOAD_FILE (file_name ) //从文件读取内容
- LOCATE (substring , string [,start_position ] ) 同INSTR,但可指定开始位置
- LPAD (string2 ,length ,pad ) //重复用pad加在string开头,直到字串长度为length
- LTRIM (string2 ) //去除前端空格
- REPEAT (string2 ,count ) //重复count次
- REPLACE (str ,search_str ,replace_str ) //在str中用replace_str替换search_str
- RPAD (string2 ,length ,pad) //在str后用pad补充,直到长度为length
- RTRIM (string2 ) //去除后端空格
- STRCMP (string1 ,string2 ) //逐字符比较两字串大小,
- SUBSTRING (str , position [,length ]) //从str的position开始,取length个字符,
- // 注:mysql中处理字符串时,默认第一个字符下标为1,即参数position必须大于等于1
- TRIM([[BOTH|LEADING|TRAILING] [padding] FROM]string2) //去除指定位置的指定字符
- UCASE (string2 ) //转换成大写
- RIGHT(string2,length) //取string2最后length个字符
- SPACE(count) //生成count个空格
- /**
- * 数学类
- */
- ABS (number2 ) //绝对值
- BIN (decimal_number ) //十进制转二进制
- CEILING (number2 ) //向上取整
- CONV(number2,from_base,to_base) //进制转换
- FLOOR (number2 ) //向下取整
- FORMAT (number,decimal_places ) //保留小数位数
- HEX (DecimalNumber ) //转十六进制
- // 注:HEX()中可传入字符串,则返回其ASC-11码,如HEX('DEF')返回4142143
- // 也可以传入十进制整数,返回其十六进制编码,如HEX(25)返回19
- LEAST (number , number2 [,..]) //求最小值
- MOD (numerator ,denominator ) //求余
- POWER (number ,power ) //求指数
- RAND([seed]) //随机数
- ROUND (number [,decimals ]) //四舍五入,decimals为小数位数]
- /**
- * 日期时间类
- */
- ADDTIME (date2 ,time_interval ) //将time_interval加到date2
- CONVERT_TZ (datetime2 ,fromTZ ,toTZ ) //转换时区
- CURRENT_DATE ( ) //当前日期
- CURRENT_TIME ( ) //当前时间
- CURRENT_TIMESTAMP ( ) //当前时间戳
- DATE (datetime ) //返回datetime的日期部分
- DATE_ADD (date2 , INTERVAL d_value d_type ) //在date2中加上日期或时间
- DATE_FORMAT (datetime ,FormatCodes ) //使用formatcodes格式显示datetime
- DATE_SUB (date2 , INTERVAL d_value d_type ) //在date2上减去一个时间
- DATEDIFF (date1 ,date2 ) //两个日期差
- DAY (date ) //返回日期的天
- DAYNAME (date ) //英文星期
- DAYOFWEEK (date ) //星期(1-7) ,1为星期天
- DAYOFYEAR (date ) //一年中的第几天
- EXTRACT (interval_name FROM date ) //从date中提取日期的指定部分
- MAKEDATE (year ,day ) //给出年及年中的第几天,生成日期串
- MAKETIME (hour ,minute ,second ) //生成时间串
- MONTHNAME (date ) //英文月份名
- NOW ( ) //当前时间
- SEC_TO_TIME (seconds ) //秒数转成时间
- STR_TO_DATE (string ,format ) //字串转成时间,以format格式显示
- TIMEDIFF (datetime1 ,datetime2 ) //两个时间差
- TIME_TO_SEC (time ) //时间转秒数]
- WEEK (date_time [,start_of_week ]) //第几周
- YEAR (datetime ) //年份
- DAYOFMONTH(datetime) //月的第几天
- HOUR(datetime) //小时
- LAST_DAY(date) //date的月的最后日期
- MICROSECOND(datetime) //微秒
- MONTH(datetime) //月
- MINUTE(datetime) //分返回符号,正负或0
- SQRT(number2) //开平方
好了,基础部分到此结束,希望对你有所帮助。