本教程以MySQL8为主版本(内容兼顾MySQL5.7)。
所有MySQL文章的目录为:总目录
存储过程(Procedure)和函数(Function)是事先编译好,并存放在数据库服务端的一段SQL代码。使用存储过程和函数可以减少应用逻辑,也减少客户端与服务器端的交互次数,提高对数据库数据的执行效率。
两者的区别有:
- 存储过程无返回值,函数必须有返回值(或者说通过参数返回)
- 存储过程的参数可以使用IN、OUT、INOUT。而函数只能使用IN
创建存储过程或函数
创建存储过程(Procedure)或函数(Function)需要用户具有Create routine权限。删除和修改需要Alter routine权限,执行需要Execute权限。创建语法为:
CREATE
[DEFINER = user]
PROCEDURE sp_name ([proc_parameter[,...]])
[characteristic ...] routine_body
CREATE
[DEFINER = user]
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:
COMMENT 'string'
| LANGUAGE SQL
| [NOT] DETERMINISTIC
| { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }
routine_body:
Valid SQL routine statement
LANGUAGE SQL是指示下面body内容使用SQL语言编写
DETERMINISTIC表示相同的输入一定得到相同的输出。默认是NOT DETERMINISTIC
SQL SECURITY 以谁的权限来表示执行该存储过程或函数:DEFINER 以创建者的权限; INVOKER 以调用者的权限。默认是DEFINER。
body代码需要使用BEGIN ... END 包起来。 语句之间的分隔符为分号;
存储过程和函数的代码中,可以调用其他的存储过程和函数。
如果在mysql客户端中执行创建语句。由于body每个语句都有分号分隔符,会被mysql认为语句结束而立即执行该语句。因此需要设置delimiter修改mysql客户端语句的结束符号。例如:
存储过程创建举例:
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> delimiter ;
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> CREATE FUNCTION hello (s CHAR(20))
-> RETURNS CHAR(50) DETERMINISTIC
-> RETURN CONCAT('Hello, ',s,'!');
-> $$
Query OK, 0 rows affected (0.01 sec)
mysql> delimiter ;
mysql> SELECT hello('world');
+----------------+
| hello('world') |
+----------------+
| Hello, world! |
+----------------+
1 row in set (0.00 sec)
可以看到存储过程和直接执行SQL语句的效果是相同的。但是存储过程的优点是,封装了处理过程,调用内不用关心内部细节。而且存储过程代码在数据库服务端,存储过程代码执行过程中不需要与客户端交互等开销。
查看存储过程和函数
查看存储过程和函数的状态
mysql> show procedure status like '%dorep%';
+------+----------+-----------+---------+---------------------+---------------------+---------------+---------+----------------------+----------------------+--------------------+
| Db | Name | Type | Definer | Modified | Created | Security_type | Comment | character_set_client | collation_connection | Database Collation |
+------+----------+-----------+---------+---------------------+---------------------+---------------+---------+----------------------+----------------------+--------------------+
| test | dorepeat | PROCEDURE | root@% | 2019-11-22 20:52:06 | 2019-11-22 20:52:06 | DEFINER | | utf8mb4 | utf8mb4_general_ci | utf8mb4_general_ci |
+------+----------+-----------+---------+---------------------+---------------------+---------------+---------+----------------------+----------------------+--------------------+
1 row in set (0.01 sec)
mysql> show FUNCTION status like '%hello%';
+------+-------+----------+---------+---------------------+---------------------+---------------+---------+----------------------+----------------------+--------------------+
| Db | Name | Type | Definer | Modified | Created | Security_type | Comment | character_set_client | collation_connection | Database Collation |
+------+-------+----------+---------+---------------------+---------------------+---------------+---------+----------------------+----------------------+--------------------+
| test | hello | FUNCTION | root@% | 2019-11-22 20:55:36 | 2019-11-22 20:55:36 | DEFINER | | utf8mb4 | utf8mb4_general_ci | utf8mb4_general_ci |
+------+-------+----------+---------+---------------------+---------------------+---------------+---------+----------------------+----------------------+--------------------+
1 row in set (0.00 sec)
查询存储过程和函数的定义
mysql> show create procedure dorepeat;
+-----------+-------------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------+----------------------+----------------------+--------------------+
| Procedure | sql_mode | Create Procedure | character_set_client | collation_connection | Database Collation |
+-----------+-------------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------+----------------------+----------------------+--------------------+
| dorepeat | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION | CREATE DEFINER=`root`@`%` PROCEDURE `dorepeat`(p1 INT)
BEGIN
SET @x = 0;
REPEAT SET @x = @x + 1; UNTIL @x > p1 END REPEAT;
END | utf8mb4 | utf8mb4_general_ci | utf8mb4_general_ci |
+-----------+-------------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------+----------------------+----------------------+--------------------+
1 row in set (0.00 sec)
mysql> show create FUNCTION hello;
+----------+-------------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------+----------------------+----------------------+--------------------+
| Function | sql_mode | Create Function | character_set_client | collation_connection | Database Collation |
+----------+-------------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------+----------------------+----------------------+--------------------+
| hello | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION | CREATE DEFINER=`root`@`%` FUNCTION `hello`(s CHAR(20)) RETURNS char(50) CHARSET utf8mb4
DETERMINISTIC
RETURN CONCAT('Hello, ',s,'!') | utf8mb4 | utf8mb4_general_ci | utf8mb4_general_ci |
+----------+-------------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------+----------------------+----------------------+--------------------+
1 row in set (0.00 sec)
还可以通过information_schema.routines查看
mysql> select * from information_schema.routines where routine_name like '%dor%' \G;
*************************** 1. row ***************************
SPECIFIC_NAME: dorepeat
ROUTINE_CATALOG: def
ROUTINE_SCHEMA: test
ROUTINE_NAME: dorepeat
ROUTINE_TYPE: PROCEDURE
DATA_TYPE:
CHARACTER_MAXIMUM_LENGTH: NULL
CHARACTER_OCTET_LENGTH: NULL
NUMERIC_PRECISION: NULL
NUMERIC_SCALE: NULL
DATETIME_PRECISION: NULL
CHARACTER_SET_NAME: NULL
COLLATION_NAME: NULL
DTD_IDENTIFIER: NULL
ROUTINE_BODY: SQL
ROUTINE_DEFINITION: BEGIN
SET @x = 0;
REPEAT SET @x = @x + 1; UNTIL @x > p1 END REPEAT;
END
EXTERNAL_NAME: NULL
EXTERNAL_LANGUAGE: NULL
PARAMETER_STYLE: SQL
IS_DETERMINISTIC: NO
SQL_DATA_ACCESS: CONTAINS SQL
SQL_PATH: NULL
SECURITY_TYPE: DEFINER
CREATED: 2019-11-22 20:52:06
LAST_ALTERED: 2019-11-22 20:52:06
SQL_MODE: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
ROUTINE_COMMENT:
DEFINER: root@%
CHARACTER_SET_CLIENT: utf8mb4
COLLATION_CONNECTION: utf8mb4_general_ci
DATABASE_COLLATION: utf8mb4_general_ci
1 row in set (0.00 sec)
ERROR:
No query specified
存储过程和函数的使用
MySQL存储过程和函数非常相似,因此简单说明如下:
- 变量定义。变量定义是放在BEGIN之后的,语法类似为:declare v_amt decimal(10,2)
- 变量赋值。使用SET 或者 select...... into 变量名语法
- 游标。游标类似存放多个数据集合变量。语法为declare cursor_name cursor for select ..... from ......
- 条件判断: IF.......THEN..... ELSEIF...... ELSE.......END IF
- 条件判断:CASE.... WHEN ..... THEN..... WHEN.....THEN.....ELSE.....END CASE
- 循环:LOOP...... END LOOP
- LEAVE:类似于java和C++中的break语句,常用从循环中退出
- ITERATE:类似于java和C++中的continue语句,终止当前循环,开始下一循环
- REPEAT循环:类似于java和C++中的while(){....}语句。 REPEAT......END REPEAT
- WHILE: WHILE DO...... END WHILE
建议:
存储过程和函数建议能不用就不用。许多银行和公司直接明文禁止使用存储过程。主要原因是:
- 一般应用逻辑已经使用Java、Python等语言开发,这时再使用存储过程和函数则相当于多了一种语言。不仅是代码分散,还需要人员团队技能增高,增加成本;
- 存储过程和函数没有生态链和扩展库。 比如Java/Python有完整的生态链和丰富的库。而存储过程和函数则缺少这些,开发稍微复杂的程序,犹如回到80年底的感觉,缺少现代的特性和组件
- 调试和问题定位比较麻烦。Java/Python有丰富的调试诊断工具,而存储过程和函数主要靠打日志,而且其日志想当简陋。 同时一段业务逻辑的运行日志,一部分在Java/Python中,一部分在存储过程和函数中。对于查问题的人来说,想当不爽。
因此实践经验中建议两条:①少用存储过程和函数;②如果必须用,则需要写特别简单(通过简单,来保证代码质量,提供稳定下,减少排查代码的痛苦)。
当然如果一个系统中(真有,国外的一个核心系统)主要逻辑都是存储和函数,相当于它最主要开发语言是。那就放开用吧。