MySQL----存储过程
基本命令
#声明变量 DECLARE a INT; #对变量赋值 SET a = 5;
基本使用
创建存储过程
DROP PROCEDURE if exists p8; CREATE PROCEDURE p8 () BEGIN DECLARE a INT; DECLARE b INT; DECLARE @TIMES INT;-- SET a = 5; SET b = 5; INSERT INTO user(name, age) VALUES (a,b); select * from user; END;
调用存储过程
call p8();
游标
- 游标的声明需要在所有其他变量之后,游标常常要配合一个状态值来实现功能,需要声明一个休止标识
- declare continue handler for not found set 休止标识=1;
- 游标使用要先open,最后需要close
- 游标值的获取用fetch my_cursor into 某变量
基本使用
delimiter $$ CREATE procedure changeName() begin declare stopflag int default 0; declare myname varchar(20) default ''; declare my_cursor cursor for select sname from student where sid%2=0; declare continue handler for not found set stopflag=1; open my_cursor; while(stopflag=0) do begin fetch my_cursor into myname; update student set sname = concat(sname,'aab') where sname = myname; end; end while; close my_cursor; end; $$
利用存储过程进行秒杀
实用有并发,但是并发量不大,请求压力在后台。如果高并发的情况下,需要做额外的处理
建表
-- 整个项目的数据库脚本 -- 开始创建一个数据库 CREATE DATABASE seckill; -- 使用数据库 USE seckill; -- 创建秒杀库存表 CREATE TABLE seckill( `seckill_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '商品库存ID', `name` VARCHAR(120) NOT NULL COMMENT '商品名称', `number` INT NOT NULL COMMENT '库存数量', `start_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() COMMENT '秒杀开启的时间', `end_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() COMMENT '秒杀结束的时间', `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() COMMENT '创建的时间', PRIMARY KEY (seckill_id), KEY idx_start_time(start_time), KEY idx_end_time(end_time), KEY idx_create_time(create_time) )ENGINE =InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='秒杀库存表'; -- 插入初始化数据 insert into seckill(name,number,start_time,end_time) values ('1000元秒杀iphone6',100,'2016-5-22 00:00:00','2016-5-23 00:00:00'), ('500元秒杀iPad2',200,'2016-5-22 00:00:00','2016-5-23 00:00:00'), ('300元秒杀小米4',300,'2016-5-22 00:00:00','2016-5-23 00:00:00'), ('200元秒杀红米note',400,'2016-5-22 00:00:00','2016-5-23 00:00:00'); -- 秒杀成功明细表 -- 用户登录相关信息 create table success_killed( `seckill_id` BIGINT NOT NULL COMMENT '秒杀商品ID', `user_phone` BIGINT NOT NULL COMMENT '用户手机号', `state` TINYINT NOT NULL DEFAULT -1 COMMENT '状态标示:-1无效 0成功 1已付款', `create_time` TIMESTAMP NOT NULL COMMENT '创建时间', PRIMARY KEY (seckill_id,user_phone), /*联合主键*/ KEY idx_create_time(create_time) )ENGINE =InnDB DEFAULT CHARSET =utf8 COMMENT ='秒杀成功明细表'
建立存储过程
-- 秒杀执行储存过程 DELIMITER $$ -- console ; 转换为 $$ -- 定义储存过程 -- 参数: in 参数 out输出参数 -- row_count() 返回上一条修改类型sql(delete,insert,update)的影响行数 -- row_count:0:未修改数据 ; >0:表示修改的行数; <0:sql错误 CREATE PROCEDURE `seckill`.`execute_seckill` (IN v_seckill_id BIGINT, IN v_phone BIGINT, IN v_kill_time TIMESTAMP, OUT r_result INT) BEGIN DECLARE insert_count INT DEFAULT 0; START TRANSACTION; #先插入,seckill_id, user_phone建立了主键,如果重复插入会报错,我们使用IGNORE,忽略重复插入,如果重复插入,SELECT row_count()返回0,就会rollback; INSERT IGNORE INTO success_killed (seckill_id, user_phone, create_time) VALUES (v_seckill_id, v_phone, v_kill_time); SELECT row_count() INTO insert_count; IF (insert_count = 0) THEN ROLLBACK; SET r_result = -1; ELSEIF (insert_count < 0) THEN ROLLBACK; SET r_result = -2; ELSE #更新仓库数量,如果更新成功,SELECT row_count()返回值>0,就可以继续走; UPDATE seckill SET number = number - 1 WHERE seckill_id = v_seckill_id AND end_time > v_kill_time AND start_time < v_kill_time AND number > 0; SELECT row_count() INTO insert_count; IF (insert_count = 0) THEN ROLLBACK; SET r_result = 0; ELSEIF (insert_count < 0) THEN ROLLBACK; SET r_result = -2; ELSE COMMIT; SET r_result = 1; END IF; END IF; END; $$
测试
set @r_result =111; CALL execute_seckill(1003, 13502172891, now(), @r_result); SELECT @r_result;
在mapper接口
void killByProcedure(Map<String,Object> paramMap);
xml
<!--调用储存过程--> <select id="killByProcedure" statementType="CALLABLE"> CALL execute_seckill( #{seckillId,jdbcType=BIGINT,mode=IN}, #{phone,jdbcType=BIGINT,mode=IN}, #{killTime,jdbcType=TIMESTAMP,mode=IN}, #{result,jdbcType=INTEGER,mode=OUT} ) </select>
ps:关于select row_count(),在控制台可以测试出来,返回上一次update,insert,delete影响行数。
if not exists注意事项
在if not exists中出现异常,不会报错,直接跳出if判断。所以很容易出现问题,尽量少用,参考上面(秒杀)使用。
delimitr $$ CREATE PROCEDURE `test_proc`( pid int ) begin if not exists (select * from t where id=pid) then insert into t(id) values(pid); end if; update t set total=total+1 where id=pid; end $$ delimiter ;
基本语法
DELIMITER 介绍
因为默认情况下 SQL 采用(;)作为结束符,这样当存储过程中的每一句 SQL 结束之后,采用(;)作为结束符,就相当于告诉 SQL 可以执行这一句了。但是存储过程是一个整体,我们不希望 SQL 逐条执行,而是采用存储过程整段执行的方式,因此我们就需要临时定义新的 DELIMITER,新的结束符可以用(//)或者($$)。
例子
DELIMITER // CREATE PROCEDURE `add_num`(IN n INT) BEGIN DECLARE i INT; DECLARE sum INT; SET i = 1; SET sum = 0; WHILE i <= n DO SET sum = sum + i; SET i = i +1; END WHILE; SELECT sum; END // DELIMITER ;
参数类型
例子
CREATE PROCEDURE `get_hero_scores`( OUT max_max_hp FLOAT, OUT min_max_mp FLOAT, OUT avg_max_attack FLOAT, s VARCHAR(255) ) BEGIN //将查询的值赋值到 max_max_hp, min_max_mp, avg_max_attack SELECT MAX(hp_max), MIN(mp_max), AVG(attack_max) FROM heros WHERE role_main = s INTO max_max_hp, min_max_mp, avg_max_attack; END
调用:
- 调用的时候需要在变量前面加 @ , 否则报错
//执行存储过程 CALL get_hero_scores(@max_max_hp, @min_max_mp, @avg_max_attack, '战士'); //查询存储过程中给我们返回的值 SELECT @max_max_hp, @min_max_mp, @avg_max_attack;
控制语句
-
BEGIN…END:BEGIN…END 中间包含了多个语句,每个语句都以(;)号为结束符。
-
DECLARE:DECLARE 用来声明变量,使用的位置在于 BEGIN…END 语句中间,而且需要在其他语句使用之前进行变量的声明。
-
SET:赋值语句,用于对变量进行赋值。
-
SELECT…INTO:把从数据表中查询的结果存放到变量中,也就是为变量赋值。
-
IF…THEN…ENDIF:条件判断语句,我们还可以在 IF…THEN…ENDIF 中使用 ELSE 和 ELSEIF 来进行条件判断。
-
CASE:CASE 语句用于多条件的分支判断,使用的语法是下面这样的。
-
LOOP、LEAVE 和 ITERATE:LOOP 是循环语句,使用 LEAVE 可以跳出循环,使用 ITERATE 则可以进入下一次循环。如果你有面向过程的编程语言的使用经验,你可以把 LEAVE 理解为 BREAK,把 ITERATE 理解为 CONTINUE。
-
REPEAT…UNTIL…END REPEAT:这是一个循环语句,首先会执行一次循环,然后在 UNTIL 中进行表达式的判断,如果满足条件就退出,即 END REPEAT;如果条件不满足,则会就继续执行循环,直到满足退出条件为止。
-
WHILE…DO…END WHILE:这也是循环语句,和 REPEAT 循环不同的是,这个语句需要先进行条件判断,如果满足条件就进行循环,如果不满足条件就退出循环。
CASE WHEN expression1 THEN ... WHEN expression2 THEN ... ... ELSE --ELSE语句可以加,也可以不加。加的话代表的所有条件都不满足时采用的方式。 END
关于存储过程使用的争议 存储过程有很多好处:
- 存储过程可以一次编译多次使用
- 存储过程的安全性强
- 可以减少网络传输量
缺点也是很明显的:
- 可移植性差
- 调试困难
- 版本管理也很困难
- 不适合高并发的场景