Skip to content Skip to footer

如何在MySQL中使用存储过程

文章目录

说明背景创建存储过程使用、删除存储过程扩展: 在存储过程中使用循环使用WHILE循环使用LOOP循环使用REPEAT循环

说明

本文所使的MySQL版本为5.7.26

背景

我们现在有这样一个需求:往t_user_view表中随机插入n条记录,其中n为受控变量。t_user_view表的建表语句如下:

CREATE TABLE `t_user_view` (

`id` BIGINT ( 20 ) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增id',

`user_id` BIGINT ( 20 ) DEFAULT NULL COMMENT '用户id',

`viewed_user_id` BIGINT ( 20 ) DEFAULT NULL COMMENT '被查看用户id',

`viewed_user_sex` TINYINT ( 1 ) DEFAULT NULL COMMENT '被查看用户性别',

`viewed_user_age` INT ( 5 ) DEFAULT NULL COMMENT '被查看用户年龄',

`create_time` datetime ( 3 ) DEFAULT CURRENT_TIMESTAMP ( 3 ),

`update_time` datetime ( 3 ) DEFAULT CURRENT_TIMESTAMP ( 3 ) ,

PRIMARY KEY ( `id` ),

UNIQUE KEY `idx_user_viewed_user` ( `user_id`, `viewed_user_id` )

) ENGINE = INNODB DEFAULT CHARSET = utf8;

这是一个很常见的需求,尤其在我们分析SQL语句的执行计划时,我们经常会面临这种数据问题(毕竟MySQL是基于成本地优化,同一个SQL语句在不同的数据量级下,优化器最终使用的执行计划可能是不同的)。而这种问题通过存储过程实现起来非常简单 。

创建存储过程

创建存储过程的基本语法如下:

CREATE PROCEDURE procedure_name ([proc_parameter[,...]]) routine_body

routine_body通常是一个使用BEGIN...END框起来的代码块

BEGIN

statesment

END

proc_parameter的定义如下

proc_parameter:

[ IN | OUT | INOUT ] param_name type

其中: IN: 表示入参,如果参数没有指定是入参还是出参,则默认为入参。入参的值在存储过程内部可以获取到,但是外部无法感知到存储过程内部对参数的修改(可以理解为编程语言中的形参)。

# 默认为IN

CREATE PROCEDURE test_in_param(param_in INT)

BEGIN

SET param_in := 2;

END

SET @param_in = 1;

# 调用 test_in_param,打印参数

CALL test_in_param(@param_in);

# 最终输出结果为 1 而不是 2

SELECT @param_in;

OUT: 表示出参,存储过程无法像程序编程那样使用return来获取返回值,因此需要使用一个变量进行接收,默认值是NULL。与IN不同,外部可以感知到该值在存储过程内部的变化。

CREATE PROCEDURE test_out_param(OUT param_out INT)

BEGIN

SET param_out := 2;

END

SET @param_out = 1;

# 调用 test_in_param,打印参数

CALL test_out_param(@param_out);

# 最终输出结果为 2

SELECT @param_out;

INOUT: 既可以表示入参,也可以表示出参。同时具备IN和OUT的功能。 现在我们开始创建业务场景中的存储过程

# 重新定义结束符

DELIMITER $$

DROP PROCEDURE IF EXISTS insertion_user;

# 这里定义了两个入参,insert_times 表示插入数据的条数,

# max_user_id 表示user_id的最大值,主要是为了能让 user_id 均匀分布

CREATE PROCEDURE insertion_user(IN insert_times INT, IN max_user_id INT)

BEGIN

DECLARE i INT DEFAULT 1;

DECLARE user_id_seq INT DEFAULT 1;

# sex 只有0和1两种取值

DECLARE sex TINYINT DEFAULT 0;

WHILE i <= insert_times

DO

# 随机生成年龄

INSERT INTO t_user_view(user_id, viewed_user_id, viewed_user_sex, viewed_user_age) VALUES (user_id_seq, i, sex, FLOOR(RAND() * 40));

SET user_id_seq := user_id_seq + 1;

SET sex := sex + 1;

# 注意这里的THEN语句后面以及END IF语句后面一定要加一个分号

IF sex % 2 = 0 THEN SET sex := 0; END IF;

IF user_id_seq = max_user_id + 1 THEN SET user_id_seq := 1; END IF;

SET i := i + 1;

END WHILE;

END$$

# 换回原来的默认结束符

DELIMITER ;

CALL insertion_user_proc(1000, 10);

使用、删除存储过程

调用存储过程非常简单,直接使用CALL pro_name([pram...]), 如上例,注意参数与参数类型要对应。

CALL insertion_user_proc(1000, 10);

删除存储过程建议使用下列语法:

DROP PROCEDURE IF EXISTS pro_name;

扩展: 在存储过程中使用循环

有三种方式可以实现循环

使用WHILE循环

语法形式为:

WHILE condition

DO

statesment

END WHILE

使用LOOP循环

语法形式为

loop_name:LOOP

statesment

IF condition THEN LEAVE loop_name; END IF;

END LOOP;

使用LOOP循环需要手动调用LEAVE来打破循环,有点类似于编程语言中的while(true)与break的组合。 示例:使用LOOP实现n的阶乘

DELIMITER $$

CREATE PROCEDURE factorial_loop_proc(up_limit INT UNSIGNED)

BEGIN

DECLARE i INT DEFAULT 1;

DECLARE result INT DEFAULT 1;

factorial_loop:LOOP

SET result := result * i;

IF i >= up_limit THEN LEAVE factorial_loop; END IF;

SET i := i + 1;

END LOOP;

SELECT result;

END$$

DELIMITER ;

CALL factorial_loop_proc(10);

使用REPEAT循环

基本语法

REPEAT

statesment

UNTIL condition END REPEAT;

有点类似于编程语言的do...while 还是以上面的阶乘为例

DELIMITER $$

CREATE PROCEDURE factorial_repeat_proc(up_limit INT UNSIGNED)

BEGIN

DECLARE i INT DEFAULT 1;

DECLARE result INT DEFAULT 1;

REPEAT

SET result := result * i;

SET i := i + 1;

UNTIL i > up_limit END REPEAT;

SELECT result;

END$$

DELIMITER ;

CALL factorial_repeat_proc(10);