Mysql - 游标/动态sql/事务

时间:2022-07-08 11:47:50

游标这个在我目前的项目里面用的还不多, 但是其功能还是很强大的.

动态sql以前都没用过, 是跟着富士康(不是张全蛋的富土康哦)过来的同事学的. 还是挺好用的. 我的数据库方面, 跟他学了不少. 在此, 感谢他一下, 建华锅锅.

事务在前面的篇章中其实已经出现过了, 这个东西好像还是程序中用的比较多一点.

由于之前的工作中碰到过一个场景, 正好将游标,动态sql,事务都用上了, 那么我也弄一个例子好了, 想了一个别的场景, 与工作的那个场景不相干, 并没有泄露公司业务机密之类的啊.

先看例子吧, 然后在后面, 我补上语法.

一、例子

1. 建表

  既然是讲例子, 当然不能忘记建表嘛, 从0开始.

create table Goods
(
Id int not null PRIMARY key auto_increment,
Code varchar() comment '编码',
Name varchar() comment '名称',
Count int comment '数量',
Brand varchar() comment '品牌'
) default charset=utf8 comment '商品表'; create table GoodDetails
(
Id int not null PRIMARY key auto_increment,
GId int not null comment 'Goods表Id',
   Name varchar() comment '名称',
Code varchar() comment '编码明细',
Remark varchar() comment '备注'
) default charset=utf8 comment '商品明细';

2. 加入基础数据

Mysql - 游标/动态sql/事务

3. 虚拟场景介绍

公司最近进了一批物品, 就是上面的Goods表了, 并且准备给每一个物品进行编码(编码规则就是用Goods表的Code加上流水号, 去生成), 并打上条形码.

这里的功能就是生成商品明细和流水号的问题了, 一键生成. 这里通常的实现方式有两种:

方式一 : 程序生成

  在程序中, 读取需要生成的数据, 比如上面这四条, 然后循环每一条, 给数据加上编码, 总共生成出12条数据, 在吧这12条数据, 存入明细表中. 在数据量少的时候还好, 完全可以接受, 但是如果数据量多了, 那速度, 慢的让人有砸电脑的冲动. Goods表的几条数据, 到GoodDetails表中, 会变成数百, 甚至上千, 上万.

方式二 : 数据库生成

  如果不想读取出来再插入, 并且逻辑处理并不多,不复杂的情况下, 可以使用数据库去生成. 还是很方便的, 速度也提升非常多.

那这里, 我只介绍方式二了, 方式一, 只是处理麻烦一点.

4. 脚本:

delimiter $
drop PROCEDURE if EXISTS p_autocreate;
CREATE PROCEDURE `p_autocreate`(IN g_ids VARCHAR(1000), IN nolength INT)
BEGIN
DECLARE res_code INT; DECLARE res_msg VARCHAR (50); /*临时表的条数*/
DECLARE t_count INT; /**游标内使用变量**begin**/
DECLARE g_id INT; DECLARE g_code VARCHAR (50); DECLARE d_code VARCHAR (50); DECLARE g_count INT (11); DECLARE g_name VARCHAR (20);
/**游标内使用变量**end**/ /**游标的位置**/
DECLARE v_index INT DEFAULT 1; DECLARE done BIT DEFAULT 0; /*声明游标*/
DECLARE g_cursor CURSOR FOR SELECT id, CODE, NAME, COUNT FROM temp_goods; /*游标查询时, 如果找不到下一个了, 会将done置为1*/
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; /**创建临时表**begin**/
DROP TABLE IF EXISTS temp_goods;
CREATE TEMPORARY TABLE temp_goods (
Id INT NOT NULL, -- PRIMARY key auto_increment,
CODE VARCHAR (50),
NAME VARCHAR (20),
COUNT INT
) DEFAULT CHARSET = utf8;
/**创建临时表**end**/ /**初始化返回值**begin**/
SET res_code := "-99"; SET res_msg := "OK";
/**初始化返回值**end**/ IF (g_ids IS NOT NULL OR LENGTH(g_ids) > 0) THEN
SET @v_sql := CONCAT("INSERT INTO temp_goods(Id,Code,Name,Count) ",
" select Id,Code,Name,Count from Goods ",
" where ",
" find_in_set(id, ",
CHAR(34),
g_ids,
CHAR(34),
")>0 ;"); /*预编译此动态sql, 并存入stmt中*/
PREPARE stmt FROM @v_sql; /*执行此动态sql, 此动态sql的作用, 是从Goods中提取有效数据*/
EXECUTE stmt; /*释放此资源*/
DEALLOCATE PREPARE stmt; SELECT COUNT(1) INTO t_count FROM temp_goods; START TRANSACTION; -- 开始事务 IF (t_count > 0) THEN
/*打开游标*/
OPEN g_cursor; REPEAT
/*这里的顺序要与之前的顺序保持一致*/
FETCH g_cursor INTO g_id, g_code, g_name, g_count; IF NOT done THEN SET v_index := 1; IF (IFNULL(g_count, 0) > 0) THEN WHILE (v_index <= g_count)
DO
SET d_code := CONCAT(g_code, LPAD(v_index, nolength, "0")); INSERT INTO GoodDetails(GId, NAME, CODE) VALUES (g_id, g_name, d_code); SET v_index := v_index + 1;
END WHILE;
END IF;
END IF;
UNTIL done END REPEAT; -- 结束repeat循环
CLOSE g_cursor; /*关闭游标*/ COMMIT; -- 提交事务 ELSE ROLLBACK; -- 回滚事务 SET res_code := "10"; SET res_msg := "系统中不存在相关记录.";
END IF;
ELSE
SET res_code := "5"; SET res_msg := "请选择要生成的记录";
END IF; DROP TABLE IF EXISTS temp_goods; SELECT res_msg;
END $
delimiter ;

5. 结果:

执行这个存储过程

call p_autocreate('1,2,3,4', 3);

Mysql - 游标/动态sql/事务

ok, 执行成功, 接下来, 来看一下GoodDetails表的数据:

Mysql - 游标/动态sql/事务

我这里的例子, 已经是最简单的一个例子了, 在实际使用过程中, 可能比这个还要复杂一些, 数据更多一些.

不过说到这个数据量, 我倒不介意, 多做一个实验.

6. 实验

  我将各自的数据量, 都修改为 10000, 如下图, 这个时候, 要生成 40000 条数据, 并且插入到表中去. 如果使用程序处理插入数据库的方式, 确实会慢一些.

  Mysql - 游标/动态sql/事务

数据库的方式, 确实会快很多. 如下图, 生成4w条数据, 然后插入GoodDetails表中, 花了不到4s的时间. 算是一个比较快的时间了.

  Mysql - 游标/动态sql/事务

OK, 接下来, 就来介绍一下他们的语法.

二、游标

1. 语法

1.1 声明游标

  declare 游标名 cursor for select 列名 from 表

1.2 打开游标

  open 游标名

1.3 游标前进

  fetch 游标名 into 变量a, 变量b ...

1.4 关闭游标

  close 游标名

2. 注

  既然游标执行的方式, 像是一个循环, 那么什么时候才知道这个循环要结束呢.

  例子里面, 有一句话,  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; 这句话的意思, 其实就是当游标找不到下一条数据的时候, 就回将变量 done 修改为1.

三、动态sql

1. 语法

1.1 准备sql变量

  此sql变量必须是字符串格式的哦. 这样可以动态生成需要执行的sql.

1.2 预编译

  PREPARE stmt FROM @v_sql;

  这里的stmt是一个变量, 名称自己取

1.3 执行

  EXECUTE stmt;

1.4 释放资源

  DEALLOCATE PREPARE stmt;

四、事务

1. 语法

到这里, 我突然不知道说些什么了. 那就简单介绍下吧

1.1 开始事务

  start transation;

1.2 提交事务

  commit;

1.3 回滚事务

  rollback;