场景
由于存储数据量太大,当一个表超过 1000w 的数据时 MySQL 性能会逐渐下降,对数据操作时非常耗时,为了优化数据库性能,使用了分表的方式每个月对数据实现水平分割,也就是说每月自动生成表一张表。
创建有参存储过程
CREATE DEFINER=`root`@`%` PROCEDURE `auto_create_month_table`(
IN `database_name` VARCHAR(50),
IN `table_name` VARCHAR(50)
)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT '每月自动生成新表'
BEGIN
-- 声明变量
DECLARE old_table_name VARCHAR(128);
DECLARE new_table_name VARCHAR(128);
DECLARE done INT DEFAULT 0;
-- 声明游标
DECLARE table_cursor CURSOR FOR SELECT TABLE_NAME FROM `information_schema`.`TABLES` WHERE TABLE_SCHEMA = database_name AND TABLE_NAME = table_name;
-- 如果没有数据返回或者 select 出现异常,程序继续,并将变量 done 设为 1
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
-- 打开游标
OPEN table_cursor;
-- 遍历游标
REPEAT
-- 获取当前游标指针记录,使用 fetch 来取出数据,取出值赋给自定义的变量
FETCH table_cursor INTO old_table_name;
IF NOT done THEN
-- 真正要操作的需求
SELECT concat(table_name,'_',DATE_FORMAT(DATE_ADD(CURDATE(),INTERVAL 1 MONTH), '%Y%m')) INTO new_table_name;
SET @sql_cmd = CONCAT('create table if not exists `',new_table_name,'` like `' , old_table_name,'`');
-- 如果需要变量返回,使用 select,如:select 变量名
-- SELECT @sql_cmd;
-- 预定义一个语句,并将它赋给 pre_stmt
PREPARE pre_stmt FROM @sql_cmd;
-- 执行语句,存储过程如果有参数,后面加参数格式为:@参数名=value,也可直接为参数值value,如果 pre_stmt 不存在,将会引发一个错误
EXECUTE pre_stmt;
-- 释放一个预定义语句的资源
DEALLOCATE PREPARE pre_stmt;
END IF ;
-- 根据 done 判断是否结束
UNTIL done END REPEAT;
-- 关闭游标
CLOSE table_cursor;
END
存储过程名称为 auto_create_month_table,参数分别为:database_name,table_name。
调用存储过程
CALL `auto_create_month_table`('test', 'temp_bigint');
注意
执行存储过程时出现一下异常
the user specified as a definer ('root'@'%') does not exist
此问题是权限问题,操作存储过程的用户不存在,授权权限即可。
添加 root 权限
grant all privileges on *.* to root@"%" identified by ".";
刷新权限
flush privileges;
声明变量
declare {变量} {数据类型}
声明变量使用 declare 命令,变量必须先声明后使用。变量是有作用域的,作用范围在 begin 与end 中使用。
default 参数的默认值。如果定义了默认值,不必指定该参数的值即可执行过程。
变量赋值
set {变量名=表达式}
-- 或
sel select{变量名=表达式}
declare 和 set 区别
- declare 的变量和参数传入的变量则必须用 concat 来连接
- set var=value 这样定义的变量直接写在字符串中就会被当作变量转换
- declare 时用来声明变量,变量默认赋值使用的 default;改变变量值需要使用 set 变量=值;
游标
游标是一个存储在MySQL服务器上的数据库查询,它不是一条 selec t语句,而是被该语句所检索出来的结果集。
定义游标
DECLARE table_cursor CURSOR FOR SELECT TABLE_NAME FROM `information_schema`.`TABLES` WHERE TABLE_SCHEMA = database_name AND TABLE_NAME = table_name;
查询 `information_schema`.`TABLES` 表的 table_name 作为游标.
存储过程异常处理
有时候,不希望存储过程抛出错误中止执行,而是希望返回一个错误码。 可以通过定义 continue/exit 异常处理的 handler 来捕获 sqlwarning/not found/sqlexception (警告/无数据/其他异常)。
for 后面可以改为 sqlwarning, not found, sqlexception 来指示所有异常都处理,当不进行异常处理时,以下代码将直接抛出一个 error 1062 (23000) 。
例:
declare continue handler for sqlstate '02000' set done = 1;
定义 continue/exit 异常处理的 handler 来捕获 sqlstate,避免抛出错误,定义一个返回参数 done 赋予特殊值来表示失败,可以通过获取返回值而不是捕获异常的方式来处理业务逻辑。
'02000' 代表发生下述异常之一:
- SELECT INTO 语句或 INSERT 语句的子查询的结果为空表
- 在搜索的 UPDATE 或 DELETE 语句内标识的行数为零
- 在 FETCH 语句中引用的游标位置处于结果表最后一行之后
当 fetch 游标到了数据库表格最后一行的时候,设置 done=1。
返回值
变量需要返回,可以使用select语音,如:select 变量名
存储过程的优缺点
优点:
- 将重复性很高的一些操作,封装到一个存储过程中,简化了对这些SQL的调用
- 存储过程是预编译过的,执行效率高
- 存储过程的代码直接存放于数据库中,通过存储过程名直接调用,减少网络通讯,节省开销;
- 可提高数据库的安全性和数据的完整性
- 存储过程可以重复使用,减少数据库开发人员的工作量
缺点:
- 如果使用大量存储过程,会使这些存储过程的每个连接的内存大大增加
- 存储过程的构造不是为开发复杂和灵活的业务逻辑而设计的
- 调试存储过程很困难
- 开发和维护存储过程并不容易
- 可移植性差