MySQL 存储过程

avatar
作者
筋斗云
阅读量:17

1.简介

存储过程(Stored Procedure)是一种存储在数据库中的程序,可供外部程序调用的一种数据库对象。

存储过程是为了完成特定功能的 SQL 语句集,提供许多过程语言的功能,例如变量定义、条件语句、循环语句、游标以及异常处理等。存储过程没有返回值,但是它可以通过输出参数实现数据的返回,同时还可以产生一个查询结果返回到客户端。

存储过程经编译创建并保存在数据库中,用户可通过指定存储过程的名字并给定参数(需要时)调用执行。

存储过程思想上很简单,就是数据库 SQL 语句的封装与重用。

MySQL 5.0 版本引入了对存储过程、存储函数和触发器等存储程序的支持。这使得开发人员能够在数据库中创建和管理复杂的业务逻辑,从而提高了数据库的功能和灵活性。

存储例程包括存储过程和函数。存储程序包括存储例程、触发器和事件。

2.优缺点

存储过程是一组可以存储在服务器中的 SQL 语句。一旦这样做了,客户端就不需要重新发出单个语句,而是可以引用存储过程。

使用存储过程在某些场景下有很多好处。

不过,存储过程也存在一些缺点:

一般来说,对于业务快速变化的互联网应用,倾向于将业务逻辑放在应用层,便于扩展。对于传统行业,或者复杂的报表分析,合理使用存储过程可以提高效率。

3.创建存储过程语法

使用 CREATE PROCEDURE 语句创建存储过程。

代码语言:javascript

复制

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。

在创建存储过程时还可以指定一些可选的属性:

示例

给定一个国家/地区代码,计算该国家/地区出现在 world 数据库的城市表中的城市数量。 使用 IN 参数传递国家/地区代码,并使用 OUT 参数返回城市计数。

代码语言:javascript

复制

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 将语句分隔符改回分号。

4.调用存储过程

使用 CALL 语句可以调用存储过程。

代码语言:javascript

复制

CALL sp_name([parameter[,...]])
CALL sp_name[()]

比如调用前文创建的查询国家/地区城市数量的存储过程,查询日本的城市数量。

代码语言:javascript

复制

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 的值,获知指定国家/地区的城市数量。

5.查看存储过程SHOW PROCEDURE STATUS

使用 SHOW PROCEDURE STATUS 语句查看存储过程列表和属性。

代码语言:javascript

复制

SHOW PROCEDURE STATUS
    [LIKE 'pattern' | WHERE expr]

这个是一个 MySQL 扩展语句。

LIKE 用于匹配存储过程的名称,WHERE 可以指定更多的过滤条件。

例如以下语句返回了存储过程 citycount 的相关信息。

代码语言:javascript

复制

mysql> SHOW PROCEDURE STATUS WHERE name = 'citycount' \G
*************************** 1. row ***************************
                  Db: world
                Name: citycount
                Type: PROCEDURE
             Definer: testuser@%
            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 语句可以查看存储过程的定义。

代码语言:javascript

复制

SHOW CREATE PROCEDURE proc_name

例如查看存储过程 citycount 的定义。

代码语言:javascript

复制

mysql> SHOW CREATE PROCEDURE test.citycount \G
*************************** 1. row ***************************
           Procedure: citycount
            sql_mode: NO_ENGINE_SUBSTITUTION
    Create Procedure: CREATE DEFINER=`testuser`@`%` 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

事实上 SHOW PROCEDURE STATUS 和 SHOW CREATE PROCEDURE 均是从系统表 INFORMATION_SCHEMA.ROUTINES 获取存储过程元信息,所以我们也可以直接查看 INFORMATION_SCHEMA.ROUTINES 表查看存储过程元信息。

6.修改存储过程

如果想修改存储过程的属性可以使用 ALTER PROCEDURE 语句。

代码语言:javascript

复制

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 系统变量来更改此行为。

7.删除存储过程

MySQL 使用 DROP PROCEDURE 语句可以删除存储过程。

代码语言:javascript

复制

DROP PROCEDURE [IF EXISTS] sp_name

如果删除不存在的存储过程会返回错误,使用 IF EXISTS 可避免该错误。

8.实例讲解

项目中需要一个脚本,需要在 MySQL 中判断表的索引是否存在,不存在则创建。

本以为 MySQL 存在以下的语句能够完成上面的功能,但是没有。

代码语言:javascript

复制

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 判断表的索引是否存在,不存在则创建的存储过程书写如下。里面很多细节需要了解,不然会出现自认为莫名其妙的问题。

代码语言:javascript

复制

---------------------------
--@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)语法太苛刻了,不支持在存储过程中有注释,比如下面简单的存储过程。

代码语言:javascript

复制

DELIMITER //
CREATE PROCEDURE HelloWorld()
BEGIN
	SELECT 'Hello World'; 
END// 
DELIMITER ;

执行截图如下:

这里写图片描述

这里写图片描述

如果在BEGIN后面加上一行注释就会出现错误。

这里写图片描述

这里写图片描述

(4)还有一个很苛刻的地方就是,MySQL 的存储过程不能写在同一行。比如将上面的 HelloWorld 存储过程写在同一行,写成如下格式执行不出错,但也没有成功,此时 MySQL 的分隔符是我们使用 DELIMITER 指定的分割符,很是奇怪,有兴趣的读者可以试一下。

代码语言:javascript

复制

DROP PROCEDURE IF EXISTS HelloWorld;
DELIMITER // CREATE PROCEDURE HelloWorld() BEGIN SELECT 'Hello World'; END// DELIMITER ;

(5)MySQL存储过程 DECLARE 和 SET 定义变量的区别。

DECLARE 语句用于在存储过程、函数、触发器等存储程序中声明局部变量。

声明的变量在存储程序的范围内有效,它们不能在存储程序外部访问。

DECLARE 声明的变量只是声明了变量的名称和数据类型,但不进行赋值。您需要在后续的逻辑中使用 SET 或其他方式为变量赋值。

代码语言:javascript

复制

--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 不仅可以用于设置局部变量的值,还可以用于设置用户变量的值(以 @ 符号开头的变量),且不需要指定类型。

代码语言:javascript

复制

--定义用户变量
SET @count=5;

总之,DECLARE 用于声明局部变量,而 SET 用于设置变量的值。这两个语句可以在不同的上下文中使用,根据需求选择使用合适的语句来定义和操作变量。

参考文献

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!