什么是存储过程?
存储过程可称为过程化SQL语言,是在普通SQL语句的基础上增加了编程语言的特点,把数据操作语句(DML)和查询语句(DQL)组织在过程化代码中,通过逻辑判断、循环等操作实现复杂计算的程序语言。
换句话说,存储过程其实就是数据库内置的一种编程语言,这种编程语言也有自己的变量、if语句、循环语句等。在一个存储过程中可以将多条SQL语句以逻辑代码的方式将其串联起来,执行这个存储过程就是将这些SQL语句按照一定的逻辑去执行,所以一个存储过程也可以看做是一组为了完成特定功能的SQL语句集。每一个存储过程都是一个数据库对象,就像table和view一样,存储在数据库当中,一次编译永久有效。并且每一个存储过程都有自己的名字。客户端程序(Java程序)通过存储过程的名字来调用存储过程。
在数据量特别庞大的情况下利用存储过程能达到倍速的效率提升。
储存过程的优点和缺点:
优点:速度快
缺点:不可移植,例如需要换orcle数据库,不能轻易切换需要重新根据orcle数据库的语法再写一遍,所以不推荐在开发中使用(如果实在不能提升效率可以考虑)并且编写难度大,维护性也差
第一个存储过程
创建存储过程确实通常会使用 BEGIN 和 END 关键字来定义存储过程中的SQL语句块。这些关键字有助于明确指示存储过程中语句的开始和结束位置,特别是在包含多条语句或复杂逻辑时
示例:
从 emp 表中查询 empno 和 ename 两个字段。过程使用了 begin 和 end 关键字来明确定义 SQL 语句的起止。这里是存储过程的具体代码:
create procedure p1()
begin
select empno, ename from emp;
end;
这个存储过程没有定义输入参数,当调用 call p1(); 时,它会执行这个 select 查询并返回 emp 表中所有行的 empno 和 ename 字段。
删除存储过程
drop procedure if exists p1;
delimiter的解释
其实就是告诉mysql解释器,该段命令是否已经结束了,mysql是否可以执行了。
默认情况下,delimiter是分号;。
delimiter //; #表示遇到俩个斜杠再执行,修改sql语句的结束符(在命令行中使用)
存储过程的查看
1. 查看创建存储过程的语句:
show create procedure p1;
2. 可以通过系统表 information_schema.routines查看存储过程的状态信息
在MySQL中,只要创建一个存储过程对象,在information_schema.routines 系统表当中就会增加一条记录,这条记录是专门描述存储过程对象状态的。
information_schema.routines 系统表当中存储的不仅仅是存储过程的状态信息,也包括函数对象,触发器对象等等状态信息
语句:
查看系统表当中所有的表:
desc information_schema.routines
指定查看某个表
SELECT *FROM information_schema.routines
WHERE routine_name = 'p1';
这里的关键字段解释如下:
- SPECIFIC_NAME:存储过程的具体名称,可以用于唯一标识过程(含参数)。
- ROUTINE_NAME:存储过程的名称(简称,不含参数)。
- ROUTINE_TYPE:例程的类型,这里是 "PROCEDURE",表示这是一个存储过程,不是函数。
- ROUTINE_SCHEMA:存储过程所在的数据库名。
- DATA_TYPE:如果是函数,会显示返回数据的类型;存储过程这一栏通常为空。
- CREATED:创建存储过程的日期和时间。
- LAST_ALTERED:最后修改存储过程的日期和时间。
- ROUTINE_DEFINITION:存储过程的定义,即具体的 SQL 代码。
如何使用这些信息
- 查询存储过程的详情:你可以通过类似的查询来了解数据库中所有存储过程的详细信息,帮助你理解和管理这些存储过程。
- 维护和更新存储过程:了解存储过程的最后修改时间和定义可以帮助你维护和更新这些存储过程。
变量
系统变量:
1. 系统变量的分类
-
全局变量(Global Variables):影响整个MySQL服务器,适用于所有的会话。全局变量的修改可能需要管理员权限,并且可以通过
SET GLOBAL
语句进行更改。全局变量在服务器启动时从配置文件加载,并且大部分全局变量可以动态修改,但有些需要重启服务器才能生效。 -
会话变量(Session Variables):每个客户端会话都有自己的会话变量副本,改变一个会话变量只会影响当前会话。可以通过
SET SESSION
语句更改会话变量,会话结束后变量会恢复默认值。
2. 查看系统变量
- 查看所有全局变量:
SHOW GLOBAL VARIABLES;
- 查看所有会话变量:
SHOW SESSION VARIABLES;
- 查看特定变量:
SHOW GLOBAL VARIABLES LIKE 'variable_name'; SHOW SESSION VARIABLES LIKE 'variable_name';
使用 LIKE
进行模糊查询
如果你大致知道变量的名称,但不确定具体名称,可以使用 LIKE
关键字进行查询。例如:
SHOW VARIABLES LIKE '%char%';
:查找包含“char”的所有系统变量。SHOW VARIABLES LIKE '%commit%';
:查找包含“commit”的所有系统变量。
3. 设置系统变量
- 设置全局变量:使用
SET GLOBAL
,通常需要管理员权限。修改后,其他新会话将使用修改后的变量值,现有会话不会受影响。SET GLOBAL max_connections = 200;
- 设置会话变量:使用
SET SESSION
,仅影响当前会话。SET SESSION sql_mode = 'STRICT_ALL_TABLES'; # 设置事务为手动提交 select @@autocommit; #这个是自动提交 select @@autocommit = 0; #这个是手动提交
如果只使用
SET
而不指定GLOBAL
或SESSION
关键字,默认情况下修改的是会话变量。
持久性
- 动态变量修改的持久性:通过
SET GLOBAL
修改的变量在服务器关闭后会失效,重新启动服务器时会从配置文件加载原始值。因此,若要使某些设置永久生效,必须修改MySQL配置文件。 - 会话变量的持久性:会话变量在每次会话开始时初始化为全局变量的值,修改会话变量不会影响其他会话或持久化。
用户变量
1. 用户变量的定义
- 用户变量以
@
符号开头,可以通过SET
或SELECT INTO
来赋值。 - 变量名是大小写不敏感的,并且可以是字母、数字或下划线的组合。
2. 用户变量的赋值方法
1) 使用 SET
语句
使用 SET
语句直接给用户变量赋值。用户变量可以存储任何数据类型,MySQL会自动根据赋值内容确定类型。
SET @var_name = value;
#设置变量值,给变量赋值
select @email = '2367889@qq.com'; #不推荐,因为在sql中,等号有判断是否相等的作用
select @email := '2367889@qq.com';
2) 使用 SELECT INTO
语句
通过 SELECT
查询结果给用户变量赋值。常用于查询单个值并保存到变量中。
SELECT column_name INTO @var_name FROM table_name WHERE condition;
# 将查询结果赋给变量
select money into @m from account where name = '张三';
#mysq1中变量不需要声明。直接赋值就行。如果没有声明变量,直接读取该变量,返回nu11
select @m;
3) 使用 :=
赋值符号(推荐使用)
在表达式中使用 :=
为用户变量赋值。这种方式经常出现在查询语句中。
SET @var_name := value;
用户变量的作用范围
- 会话范围:用户变量的作用范围仅限于当前会话(连接)。一旦会话结束,用户变量也随之销毁。
- 跨查询使用:用户变量可以在同一会话的多个查询中使用,起到临时存储数据的作用。
注意事项
-
未定义的用户变量:在使用一个未初始化的用户变量时,MySQL不会报错,而是自动将其值设为
NULL
。SELECT @undefined_var; -- 结果为 NULL
-
赋值后的值变化:一旦用户变量被赋值后,可以在整个会话中反复使用。会话结束后变量自动消失。
-
性能问题:用户变量存储在内存中,如果使用太多可能会导致内存占用过多,因此在复杂应用中应谨慎使用。
局部变量:
1. 局部变量的定义
局部变量必须在存储过程、函数或触发器的声明部分显式声明,使用 DECLARE
关键字进行定义。
DECLARE var_name datatype [DEFAULT value];
var_name
:变量名。datatype
:变量的数据类型,必须是MySQL支持的数据类型,例如INT
、VARCHAR
、DECIMAL
等。DEFAULT value
:可选,变量的默认值。如果不指定默认值,变量的初始值为NULL
。
注意:declare通常出现在begin end之间的开始部分
与用户变量的差别:
定义方式 | @var_name | DECLARE var_name |
作用范围 | 当前会话 | 存储过程、函数或触发器的执行体内 |
初始化 | 不需要显式初始化,未赋值前为 NULL | 必须声明和初始化 |
使用场景 | 在普通SQL查询中使用,跨查询 | 只能在存储过程、函数或触发器中使用 |
CREATE PROCEDURE p3()
BEGIN
/* 声明局部变量 */
DECLARE num INT DEFAULT 0;
DECLARE mone INT DEFAULT 0;
/* 给局部变量赋值 */
SELECT COUNT(*) INTO num FROM account; -- 统计 account 表中的用户数
SET mone := 50; -- 将 mone 设置为 50
/* 读取局部变量的值 */
SELECT num, mone; -- 输出 num 和 mone 的值
END;
# 调用存储过程
call p3();
/*局部变量只在存储过程中有效,这里是无法访问的*/
select num,mone;
MySQL中的语句:
if语句:
判断一个数字是否为正数、负数或零。
CREATE PROCEDURE check_simple()
BEGIN
declare num int default 5;
if num > 0 then
SELECT 'Positive';
elseif num < 0 THEN
SELECT 'Negative';
else
SELECT 'Zero';
end if;
END;
#调用存储过程
call check_simple();
参数:
参数类型说明:
IN
:输入参数,存储过程调用时传入的值,默认就是IN
。OUT
:输出参数,存储过程会通过该参数输出结果。INOUT
:即输入又输出,调用时传入值,存储过程修改后返回。
drop procedure check_simple2;
CREATE PROCEDURE check_simple2(in num int, out ans varchar(10))
BEGIN
if num > 0 then
set ans := 'Positive';
elseif num < 0 THEN
set ans := 'Negative';
else
set ans := 'Zero';
end if;
END;
# 不能用局部变量,需要用用户变量
call check_simple2(10,@ans);
select @ans;
case语句:
CREATE PROCEDURE GetMonthName(IN month_num INT, OUT month_name VARCHAR(20))
BEGIN
CASE month_num
WHEN 1 THEN SET month_name = 'January';
WHEN 2 THEN SET month_name = 'February';
WHEN 3 THEN SET month_name = 'March';
WHEN 4 THEN SET month_name = 'April';
WHEN 5 THEN SET month_name = 'May';
WHEN 6 THEN SET month_name = 'June';
WHEN 7 THEN SET month_name = 'July';
WHEN 8 THEN SET month_name = 'August';
WHEN 9 THEN SET month_name = 'September';
WHEN 10 THEN SET month_name = 'October';
WHEN 11 THEN SET month_name = 'November';
WHEN 12 THEN SET month_name = 'December';
ELSE SET month_name = 'Invalid Month';
END CASE;
END;
调用存储过程:
-- 定义输出变量
SET @result = '';
-- 调用存储过程,传入月份编号
CALL GetMonthName(5, @result);
-- 查看返回的结果
SELECT @result;
CREATE PROCEDURE p8(IN month INT, OUT season VARCHAR(20))
BEGIN
CASE
WHEN month BETWEEN 3 AND 5 THEN
SET season := '春季'; -- 春季
WHEN month BETWEEN 6 AND 8 THEN
SET season := '夏季'; -- 夏季
WHEN month BETWEEN 9 AND 11 THEN
SET season := '秋季'; -- 秋季
WHEN month = 12 OR month = 1 OR month = 2 THEN
SET season := '冬季'; -- 冬季
ELSE
SET season := '无效的月份'; -- 无效的月份
END CASE;
END;
-- 调用存储过程
CALL p8(12, @season); -- 输入月份12
SELECT @season; -- 输出季节
while循环:
基本语法
WHILE condition DO
-- 循环执行的语句
END WHILE;
- condition:这是循环的条件,返回
true
时,循环将继续执行。如果条件返回false
,循环将结束。 - DO...END WHILE:表示在
DO
和END WHILE
之间的代码是循环体,即在每次循环迭代中会执行的内容。
查找1~num中的所有偶数和
# while 循环
drop procedure p5;
create procedure p5(in num int ,out sum int)
begin
# 初始化sum,否则sum始终为0
set sum := 0;
while num > 0 do
if num % 2 = 0 then
set sum := sum + num;
end if;
set num := num - 1;
end while;
end;
repeat循环:
REPEAT
循环是 MySQL 中的另一种循环控制结构,与 WHILE
循环类似,用于重复执行一段代码,直到满足指定的条件。不同之处在于,REPEAT
循环会先执行一次代码块,然后检查条件,条件为 true
时退出循环。因此,它至少会执行一次。
基本语法:
REPEAT
-- 循环执行的语句
UNTIL condition
END REPEAT;
UNTIL condition
:表示循环何时结束。当condition
为true
时,循环终止。REPEAT...UNTIL
:表示先执行循环体中的代码,再判断是否满足退出条件。
REPEAT
循环与 WHILE
循环的区别:
REPEAT
循环是先执行后判断,所以即使一开始条件不满足,循环体中的代码也会至少执行一次。WHILE
循环是先判断条件再执行,如果条件一开始就不满足,循环体中的代码一次也不会执行。
1. 计算从 1 到 n 的数字之和
# repeat循环
drop procedure p6;
create procedure p6(in n int,out sum int)
begin
set sum := 0;
repeat
if n % 2 = 0 then
set sum := sum + n;
end if;
set n := n -1;
until n <= 0 #注意这里不能添加分号,满足条件就结束循环
end repeat;
end;
call p6(6,@sumRepeat);
select @sumRepeat;
loop循环:
在 MySQL 中,LOOP
是最基本的循环控制结构之一,它会一直执行循环体中的语句,直到使用 LEAVE
语句退出循环。LOOP
循环通常用于需要手动控制循环终止条件的场景,和 WHILE
以及 REPEAT
循环相比,LOOP
循环不依赖条件,而是必须手动退出。
label_name: LOOP
-- 循环体的语句
IF condition THEN
LEAVE label_name; -- 当条件满足时,退出循环
END IF;
END LOOP;
# loop循环 输出1~9,并且不输出5
drop procedure p7;
create procedure p7()
begin
declare i int default 0;
myloop : loop
set i := i + 1;
# 判断是否为5,iterate相当于continue
if i = 5 then
iterate myloop;
end if;
if i = 10 then
leave myloop;
end if;
select i;
end loop;
end;
call p7();
存储过程的游标
在数据库的存储过程中,游标是一个非常有用的工具,它允许程序逐行处理查询结果中的数据。这在需要对查询结果的每一行进行复杂处理或计算时尤其有用。下面是使用游标的一些基本步骤:
1. 声明游标:首先需要声明游标,并定义它将执行的查询。例如,在SQL Server中,可以这样声明一个游标:注意:声明游标需要再声明普通变量之后
DECLARE my_cursor CURSOR FOR
SELECT column1, column2 FROM my_table WHERE condition;
- DECLARE my_cursor CURSOR FOR:这是声明游标的开始。my_cursor 是游标的名称,你可以用它来引用和操作游标。
- SELECT column1, column2 FROM my_table WHERE condition;:这部分是一个 SQL 查询语句,它定义了游标将要检索的数据。游标 my_cursor 会访问 my_table 表,并且只选择那些满足 condition 条件的行。在这个查询中,它只会检索 column1 和 column2 这两列的数据。
2. 打开游标:一旦声明了游标,就需要打开它以开始访问行。
OPEN my_cursor;
3. 获取数据:可以逐行从游标中提取数据。通常,这会涉及到一个循环,每次循环读取一行数据。
FETCH FROM my_cursor INTO @variable1, @variable2;
这里`@variable1`和`@variable2`是存储从游标中获取的数据的变量。这里把获取的一行的俩列储存在变量中
4. 循环处理数据:一般使用循环(如`WHILE`)来持续从游标中提取数据,并对每行数据进行处理。此处没有处理死循环,将在异常处理改进
drop procedure if exists test;
create procedure test()
begin
/* 声明变量 */
declare name varchar(10) default null;
declare money int default 0;
declare done boolean default true;
/* 声明游标 */
declare test_cursor cursor for select account.name,account.money from account;
/* 打开游标 */
open test_cursor;
/* 使用游标 */
while true do
fetch test_cursor into name,money;
select name,money;
end while;
/* 关闭游标 */
close test_cursor;
end;
call test();
出现问题
account表中数据如下:
但是输出:
原因:
当前代码中的 while true do
循环没有判断 fetch
是否成功。因为当游标遍历到结果集的末尾时,fetch
不会成功,也不会自动退出循环,导致程序只输出第一个人后陷入无限循环。
5. 关闭游标:处理完所有数据后,关闭游标是一个好习惯。
CLOSE my_cursor;
游标虽然在处理逐行数据时非常有用,但通常来说,它们比批量操作或集合操作要慢。因此,除非确实需要对数据进行逐行处理,否则推荐尽量使用更优化的集合操作来提高性能。
存储过程的异常处理
DECLARE {EXIT | CONTINUE} HANDLER FOR condition_value action_statement;
主要组成部分:
- condition_value:定义处理的条件类型,例如:
SQLEXCEPTION
:捕获所有 SQL 异常,除了SQLWARNING
和NOT FOUND
。SQLWARNING
:捕获所有 SQL 警告。NOT FOUND
:处理未找到数据或游标到达末尾的情况。SQLSTATE 'value'
:指定具体的 SQLSTATE 错误码。
- action_statement:当条件触发时执行的操作,例如:CLOSE cursor_name
drop procedure if exists test;
create procedure test()
begin
/* 声明变量 */
declare name varchar(10) default null;
declare money int default 0;
declare done boolean default true;
/* 声明游标 */
declare test_cursor cursor for select account.name,account.money from account;
/* 声明退出处理 */
declare exit handler for not found set done = 1;
/* 打开游标 */
open test_cursor;
/* 使用游标 */
while done do
fetch test_cursor into name,money;
select name,money;
end while;
/* 关闭游标 */
close test_cursor;
end;
call test();
存储函数
1. 存储函数:
存储函数是带有返回值的存储过程,它的参数只允许是 `IN`(输入参数)但是IN不能显示声明,没有 `OUT` 或 `INOUT` 参数。语法格式如下:
CREATE FUNCTION function_name (parameter_list) RETURNS data_type [characteristics]
BEGIN
-- function body
RETURN ...;
END;
2. 特征:(提高效率)
-
deterministic: 确定性函数。每次传递相同的参数时,返回的结果是固定的。标记为确定性函数可以帮助优化器进行缓存,提高执行效率。
-
no sql: 函数不涉及任何 SQL 语句,即不执行数据库查询操作。该特性告知 MySQL 优化器不需要为该函数考虑查询缓存或其他数据库优化措施。
-
reads sql data: 函数可以执行只读 SQL 查询,告知 MySQL 优化器该函数会访问数据库,但不执行写操作。
/* 存储函数 案例:计算1~n的所有偶数之和 */ drop function if exists sum_fun; create function sum_fun(n int) returns int DETERMINISTIC begin declare result int default 0; while n > 0 do if n % 2 = 0 then set result := result + n; end if; set n := n - 1; end while; return result; end; /* 调用存储函数 */ set @result = sum_fun(100); select @result;