存储过程(Stored Procedure)是一种存储在数据库中的程序,以便外部程序调用的一种数据库对象。
存储过程是为了完成特定功能的 SQL 语句集,提供许多过程语言的功能,例如变量定义、条件语句、循环语句、游标以及异常处理等。存储过程没有返回值,但是它可以通过输出参数实现数据的返回,同时还可以产生一个查询结果返回到客户端。
存储过程经编译创建并保存在数据库中,用户可通过指定存储过程的名字并给定参数(需要时)来调用执行。
存储过程思想上很简单,就是数据库 SQL 语言层面的代码封装与重用。
MySQL 5.0 版本引入了对存储过程、存储函数和触发器等存储程序的支持。这使得开发人员能够在数据库中创建和管理复杂的业务逻辑,从而提高了数据库的功能和灵活性。
存储例程是一组可以存储在服务器中的 SQL 语句。一旦这样做了,客户端就不需要重新发出单个语句,而是可以引用存储例程。
存储例程包含存储过程和存储函数。
使用存储例程在某些场景下有很多好处。
使用 CREATE PROCEDURE 语句创建存储过程。
CREATE
[DEFINER = user]
PROCEDURE [IF NOT EXISTS] sp_name ([proc_parameter[,...]])
[characteristic ...] routine_body
proc_parameter:
[ IN | OUT | INOUT ] 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
DEFINER 子句用于指定存储过程的创建者或拥有者,也就是定义存储过程的用户。作用是确定存储过程在执行时所具有的执行者的权限和权限级别。存储过程在执行时将使用 DEFINER 指定的用户的权限来执行,而不是调用存储过程的用户的权限。这可以用于实现数据库的安全性和隔离。
proc_parameter 为存储过程的参数,使用 IN(输入)、OUT(输出)或 INOUT(输入输出)修饰。如果不显示指定,缺省为 IN。
在创建存储过程时还可以指定一些可选的属性:
COMMENT 可以为存储过程添加注释。
LANGUAGE表示编写存储过程的语言,目前 SQL 是唯一可用值。
DETERMINISTIC 属性表示这是一个确定性存储过程,对于相同的输入参数一定会返回相同的结果;MySQL 缺省是非确定性(NOT DETERMINISTIC)。
CONTAINS SQL 表示程序中不包含读取或者写入数据表的语句,这是默认设置。举例来说,SET @x = 1 或者 DO RELEASE_LOCK(‘abc’) 不会读写任何数据。NO SQL 表示程序不包含任何 SQL 语句。READS SQL DATA 表示程序包含读取操作(例如 SELECT),但不会修改数据表。MODIFIES SQL DATA 表示程序包含写入操作(例如 INSERT 或者 DELETE)。这些属性仅供 MySQL 服务器参考使用,不会用于限制程序中实际使用的语句。
SQL SECURITY 属性表示存储过程以定义者权限(DEFINER )还是调用者权限(INVOKER )执行。
下面是一个创建存储过程的示例。
给定一个国家/地区代码,计算该国家/地区出现在 world 数据库的城市表中的城市数量。 使用 IN 参数传递国家/地区代码,并使用 OUT 参数返回城市计数。
mysql> DELIMITER //
mysql> CREATE PROCEDURE citycount (IN country CHAR(3), OUT cities INT)
BEGIN
SELECT COUNT(*) INTO cities FROM world.city
WHERE CountryCode = country;
END//
Query OK, 0 rows affected (0.01 sec)
mysql> DELIMITER ;
如果使用 mysql 客户端程序定义一个包含分号字符的存储程序,就会出现问题。 默认情况下,mysql 本身将分号识别为语句分隔符,因此必须临时重新定义分隔符,以使 mysql 将整个存储程序定义传递到服务器。
所以上面的示例中,我们使用 DELIMITER 重新定义语句分隔符为 //,定义存储过程使用 // 结束,最后再使用 DELIMITER 将语句分隔符改回分号。
使用 CALL 语句可以调用存储过程。
CALL sp_name([parameter[,...]])
CALL sp_name[()]
比如调用前文创建的查询国家/地区城市数量的存储过程,查询日本的城市数量。
mysql> CALL citycount('JPN', @cities); -- cities in Japan
Query OK, 1 row affected (0.00 sec)
mysql> SELECT @cities;
+---------+
| @cities |
+---------+
| 248 |
+---------+
1 row in set (0.00 sec)
在 MySQL 中,@ 符号用于定义和使用用户变量。用户变量是一种在会话中存储和操作数据的机制,它们在 SQL 查询中可以使用,但并不与数据库表中的列或行直接关联。
调用存储过程传入的 @cities 表示接收城市数量的用户变量。调用完存储过程后,通过 SELECT 可以查询变量 @cities 的值,获知指定国家/地区的城市数量。
使用 SHOW PROCEDURE STATUS 语句查看存储过程列表和属性。
SHOW PROCEDURE STATUS
[LIKE 'pattern' | WHERE expr]
LIKE 用于匹配存储过程的名称,WHERE 可以指定更多的过滤条件。
例如以下语句返回了存储过程 citycount 的相关信息。
mysql> SHOW PROCEDURE STATUS WHERE name = 'citycount' \G
*************************** 1. row ***************************
Db: world
Name: citycount
Type: PROCEDURE
Definer: foo@%
Modified: 2023-08-21 11:57:50
Created: 2023-08-21 11:57:50
Security_type: DEFINER
Comment:
character_set_client: utf8
collation_connection: utf8_general_ci
Database Collation: utf8mb4_general_ci
返回的信息包括数据库、存储过程名称、类型(PROCEDURE)、创建者、创建时间和修改时间、调用权限以及字符集信息。
使用 SHOW CREATE PROCEDURE 语句可以查看存储过程的定义。
SHOW CREATE PROCEDURE proc_name
例如查看存储过程 citycount 的定义。
mysql> SHOW CREATE PROCEDURE test.citycount \G
*************************** 1. row ***************************
Procedure: citycount
sql_mode: NO_ENGINE_SUBSTITUTION
Create Procedure: CREATE DEFINER=`foo`@`%` PROCEDURE `citycount`(IN country CHAR(3), OUT cities INT)
BEGIN SELECT COUNT(*) INTO cities FROM world.city WHERE CountryCode = country; END
character_set_client: utf8
collation_connection: utf8_general_ci
Database Collation: utf8mb4_general_ci
另外,我们也可以通过系统表 information_schema.routines 查看存储过程和函数的信息。
如果想修改存储过程的属性可以使用 ALTER PROCEDURE 语句。
ALTER PROCEDURE proc_name [characteristic ...]
characteristic: {
COMMENT 'string'
| LANGUAGE SQL
| { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }
}
上面的语句不能更改存储过程的参数或主体。要进行此类更改,必须使用 DROP PROCEDURE 和 CREATE PROCEDURE 删除并重新创建该存储过程。
您必须具有该过程的 ALTER ROUTINE 权限。 默认情况下,该权限会自动授予过程创建者。 可以通过禁用 automatic_sp_privileges 系统变量来更改此行为。
MySQL 使用 DROP PROCEDURE 语句可以删除存储过程。
DROP PROCEDURE [IF EXISTS] sp_name
如果删除的存储过程不存在,将会返回一个错误信息;使用 IF EXISTS 可以避免该错误。
项目中需要一个脚本,需要在 MySQL 中判断表的索引是否存在,不存在则创建。
本以为 MySQL 存在以下的语句能够完成上面的功能,但是没有。
IF NOT EXISTS(SHOW INDEX FROM tbl_name) THEN ALTER TABLE tbl_name ADD INDEX(column_list) END IF;
# 或
CREATE INDEX index_name ON tbl_name (column_list) IF NOT EXISTS index_name;
可惜啊!MySQL 目前并不支持在 SQL 语句中存在流控制语句,例如上面的IF NOT EXISTS THEN END IF
;让人痛心疾首。但是我们可以使用存储过程完成上面要求的功能。
MySQL 判断表的索引是否存在,不存在则创建的存储过程书写如下。里面很多细节需要了解,不然会出现自认为莫名其妙的问题。
---------------------------
--@brief:判断指定数据表的索引是否存在,如果不存在则创建
--@param:tableName:数据表名;idxName:索引名;columnName:建立索引的列名
---------------------------
DROP PROCEDURE IF EXISTS proc_addIndex;
DELIMITER //
create procedure proc_addIndex(IN tableName VARCHAR(64),IN idxName VARCHAR(200),IN columnName VARCHAR(64))
BEGIN
IF NOT EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema="databaseName" AND table_name=tableName AND index_name=idxName)
THEN
SET @sqlStr=CONCAT("alter table ",tableName," add index(",columnName,")");
PREPARE sqlStatement from @sqlStr;
EXECUTE sqlStatement;
DEALLOCATE PREPARE sqlStatement;
END IF;
END//
DELIMITER ;
阅读上面的代码需要注意如下几个问题。
(1)MySQL 存储过程中是不支持表名和列名作为变量。如果表名和列名作为参数的话,只有通过 CONCAT 函数拼接动态 SQL 字符串,使用 PREPARE 语句预处理后,再由 EXECUTE 来执行。但是在 IF EXISTS 或者 IF NOT EXISTS 中 SQL 语句作为条件出现时,表名和列名可以作为变量。
(2)DELIMITER // 用于改变 SQL 语句分隔符为 //,否则 mysql 在回车的情况下会按照默认分割符分号将存储过程拆分,使其执行失败。存储过程结束后,再次使用 DELIMITER 将语句分隔符改回分号。注意 DELIMITER 和分隔符之间要有空格。
(3)MySQL(5.7.38)语法太苛刻了,不支持在存储过程中有注释,比如下面简单的存储过程。
DELIMITER //
CREATE PROCEDURE HelloWorld()
BEGIN
SELECT 'Hello World';
END//
DELIMITER ;
执行截图如下:
如果在BEGIN后面加上一行注释就会出现错误。
(4)还有一个很苛刻的地方就是,MySQL 的存储过程不能写在同一行。比如将上面的 HelloWorld 存储过程写在同一行,写成如下格式执行不出错,但也没有成功,此时 MySQL 的分隔符是我们使用 DELIMITER 指定的分割符,很是奇怪,有兴趣的读者可以试一下。
DROP PROCEDURE IF EXISTS HelloWorld;
DELIMITER // CREATE PROCEDURE HelloWorld() BEGIN SELECT 'Hello World'; END// DELIMITER ;
(5)MySQL存储过程 DECLARE 和 SET 定义变量的区别。
DECLARE 语句用于在存储过程、函数、触发器等存储程序中声明局部变量。
声明的变量在存储程序的范围内有效,它们不能在存储程序外部访问。
DECLARE 声明的变量只是声明了变量的名称和数据类型,但不进行赋值。您需要在后续的逻辑中使用 SET 或其他方式为变量赋值。
--DECLARE 定义变量
DECLARE var_name [datatype(size)] DEFAULT [default_value];
--例如
DECLARE count INT DEFAULT 0;
--使用set赋值
SET count=5;
--还可以通过 SELECT INTO 语句将返回的值赋给变量
SELECT COUNT(*) INTO count FROM tbl_name;
--可同时定义多个变量
DECLARE x, y INT DEFAULT 0
SET 语句用于在查询或存储程序中给已声明的变量赋值。
它可以用于在任何上下文中设置变量的值,无论是在查询中还是在存储过程中。
SET 不仅可以用于设置局部变量的值,还可以用于设置用户变量的值(以 @ 符号开头的变量),且不需要指定类型。
--定义用户变量
SET @count=5;
总之,DECLARE 用于声明局部变量,而 SET 用于设置变量的值。这两个语句可以在不同的上下文中使用,根据需求选择使用合适的语句来定义和操作变量。
MySQL 8.0 Reference Manual :: 25 Stored Objects
MySQL 8.0 Reference Manual :: 25.2 Using Stored Routines
13.1.17 CREATE PROCEDURE and CREATE FUNCTION …
13.7.7.28 SHOW PROCEDURE STATUS Statement
13.7.7.9 SHOW CREATE PROCEDURE Statement
13.1.7 ALTER PROCEDURE Statement
13.1.29 DROP PROCEDURE and DROP FUNCTION Statements
《MySQL 入门教程》第 31 篇 存储过程(一)
MySQL存储过程详解
mysql存储过程中 传递表名作参数怎么整