- 🏠博客主页:小谢同学的小破站
- ✍️本文由小谢同学的小破站原创,首发于 CSDN 💻
- ☕JavaSE专栏:JavaSE
- 📗JavaEE初阶专栏:JavaEE初阶
- 📘JavaEE进阶专栏:JavaEE进阶
- 🧩数据结构专栏:数据结构
- ⚙️算法专栏:算法
- 📚MySQL初阶专栏:MySQL初阶
- 🔐MySQL进阶专栏:MySQL进阶
- 🌐计算机网络专栏:计算机网络
- 💻C语言专栏:C语言
- 👍欢迎点赞👍 收藏⭐ 留言📝,发现错误欢迎指正!
- ✨脚踏实地,持续深耕,奔赴自己的目标✨
----- 📌分割线 📌-------
游标与条件处理程序
1. 游标(cursor)
- 1.1 游标完整四步:声明、open、fetch、close
- 1.2 游标基础示例代码
- 1.3 游标缺点:内存、不适合大表
2. 条件处理程序 HANDLER
- 2.1 语法讲解 DECLARE … HANDLER
- 2.2 游标必踩坑:fetch读完数据报1329报错,HANDLER处理结束标记
- 2.3 HANDLER的触发时机、continue和exit区别
3. 条件处理程序 HANDLER
- 3.1 游标 + 条件处理程序完整可运行存储过程(逐行遍历表并退出~)
前言:
这里是小谢同学整理有关MySQL中游标的概念。笔记用于自我复盘巩固,有错误欢迎大家指出,专栏还有 Java、网络、C 语言系列笔记欢迎翻阅.同时也希望这篇文章能够帮助到你~
1. 游标(cursor)
MySQL 游标是一种数据库对象,用于在存储过程或函数中逐行遍历查询结果集,以便对每条记录进行处理。
游标(Cursor)不是单独的 SELECT 语句,而是由 SELECT 查询返回的结果集的指针。
它允许程序逐行访问数据,而不是一次性处理整个结果集,这在处理大量数据或需要对每行执行复杂逻辑时非常有用MySQL 中的游标只能在存储过程和函数中使用。
1.1 游标完整四步:声明、open、fetch、close
游标必须在条件处理程序之前被声明,并且变量必须在游标活条件处理程序之前被声明
例如:
我们想在一个存储过程中定义变量,我们需要在写存储过程中最前面先定义出我们所需要的变量
如果后续我们想要添加某一个变量,我们可以直接在没有创建存储过程之前来添加,方便进行统一管理
变量->游标->条件处理语句
语法:
-- 1.声明游标DECLAREcursor_nameCURSORFORselect_statement;-- 2.打开游标OPENcursor_name;-- 3.读取一行FETCHcursor_nameINTOvar_name[,var_name]...;-- 4.关闭游标CLOSEcursor_name;解释
- select_statement: 查询语句
- cursor_name : 游标名称
- var_name 读取对应的字段
- var_name [, var_name] … :查询放入的变量
1.2 游标基础示例代码
例如:
传入班级编号,查询学生表中属于该班级学生的信息,并将符合条件的学生信息写入一张新表中;
- 新表及字段t_student_class(id,student_name,class_name)
实现逻辑:
- 定义变量来接收查询结果集中的每一列的值
- 声明游标
- 创建新表
- 开启游标
- 从游标中获取结果集中的记录
- 插入新表
- 关闭游标
对应表的查询结果:
class:
student:
DELIMITER//CREATEPROCEDUREp7(INclass_idINT)BEGIN-- 创建我们所需要的变量-- 学生姓名DECLAREstudent_nameVARCHAR(20);-- 班级名字DECLAREclass_namevarchar(20);-- 声明游标DECLAREs_cursorCURSORFORSELECTs.name,ds.nameFROMstudentASsJOINclassASdsONs.class_id=ds.idwhereds.id=class_id;-- 创建新表CREATETABLEIFNOTEXISTSt_student_class(idINTPRIMARYKEYAUTO_INCREMENT,student_nameVARCHAR(20),class_nameVARCHAR(20));-- 打开游标OPENs_cursor;WHILETRUEDO-- 获取游标的内容FETCHs_cursorINTOstudent_name,class_name;-- 插入新表INSERTINTOt_student_classVALUES(null,student_name,class_name);ENDWHILE;END//DELIMITER;CALLp7(1);
此处报错了:1329的典型错误
由于while循环的退出条件是true,此时是⼀个死循环,当游标遍历完成之后继续向后遍历,发现没
有记录,所以报错,可以通过条件处理程序解决
我们此时怎么解决这个问题?
答案: 条件处理程序 HANDLER
1.3 游标缺点:内存、不适合大表
- 消耗内存:OPEN 的时候会把全部查询结果集加载到内存,数据越多占用内存越高。
- 性能差,不适合大表:
游标是逐行处理,行越多速度越慢;千万级大表严禁游标。
MySQL 设计优先集合操作,能用
UPDATE / INSERT ... SELECT批量就不要游标逐行。
- 只能在存储过程、存储函数内部使用,SQL 语句不能直接写游标。
2. 条件处理程序 HANDLER
- 定义条件:事先定义程序执行过程中可能遇到的问题
- 处理程序定义了在遇到问题的时候采取的处理方式
- 使⽤条件处理程序保证存储过程或函数在遇到警告或错误时能继续执⾏,可以增强程序处理问题的
能⼒,避免程序异常停⽌运⾏。
2.1 语法讲解 DECLARE ... HANDLER
语法:
DECLAREhandler_actionHANDLERFORcondition_value[,condition_value]...statement;handler_action: {CONTINUE-- 继续执行当前程序|EXIT-- 终止执行当前程序} condition_value: { mysql_error_code-- MySQL错误码|SQLSTATE[VALUE]sqlstate_value-- 状态码|SQLWARNING-- 所有以01开头的SQLSTATE代码|NOTFOUND-- 所有以02开头的SQLSTATE代码|SQLEXCEPTION-- 所有没有被SQLWARNING或NOT FOUND捕获的SQLSTATE代码}
- mysql_error_code(数字错误码)
- SQLSTATE [VALUE] sqlstate_value(5 位状态字符串)
- SQLWARNING → 匹配所有
01开头 SQLSTATE
全部警告类,不会终止程序的警告。- NOT FOUND → 匹配所有
02开头 SQLSTATE(最典型就是02000:FETCH 游标,已经读到数据集末尾,找不到下一行。)- SQLEXCEPTION
除去 01 开头 (SQLWARNING)、02 开头 (NOT FOUND) 之外剩下全部错误。
比如主键冲突、表不存在、语法错误,全部归 SQLEXCEPTION。
2.2 游标必踩坑:fetch读完数据报1329报错,HANDLER处理结束标记
我们上述所见到的while循环写成死循环的情况,此时就出现了1329错误的状态码;
原因归根结底就是:我们没有写条件处理程序而导致程序不能正常执行~
2.3 HANDLER的触发时机、continue和exit区别
| 类型 | 行为 | 适用场景 |
|---|---|---|
CONTINUE | 捕获异常,执行处理代码,继续向下跑 | 仅仅记录错误,存储过程还要继续执行后续逻辑 |
EXIT | 捕获异常,执行处理代码,立刻退出当前 begin‑end 块 | 出错直接结束流程,不再执行后面代码 |
触发时机:只有执行语句抛出对应条件时才触发 handler,不是提前检测。游标 fetch 拿不到数据那一刻触发 NOT FOUND。
3. 条件处理程序 HANDLER
使用条件处理程序来解决while循环出现的问题,防止游标在末尾而出现的问题:
DELIMITER//CREATEPROCEDUREp7(INclass_idINT)BEGIN-- 创建我们所需要的变量-- 学生姓名DECLAREstudent_nameVARCHAR(20);-- 班级名字DECLAREclass_namevarchar(20);-- 创建判断条件DECLAREis_doneboolDEFAULTFALSE;-- 声明游标DECLAREs_cursorCURSORFORSELECTs.name,ds.nameFROMstudentASsJOINclassASdsONs.class_id=ds.idwhereds.id=class_id;-- 创建条件处理程序DECLARECONTINUEHANDLERFORNOTFOUNDSETis_done :=TRUE;-- 创建新表CREATETABLEIFNOTEXISTSt_student_class(idINTPRIMARYKEYAUTO_INCREMENT,student_nameVARCHAR(20),class_nameVARCHAR(20));-- 打开游标OPENs_cursor;WHILENOTis_doneDO-- 获取游标的内容FETCHs_cursorINTOstudent_name,class_name;-- 插入新表INSERTINTOt_student_classVALUES(null,student_name,class_name);ENDWHILE;END//DELIMITER;CALLp7(1);SELECT*FROMt_student_class;但是此时问题又来了:
为什么最后一条数据出现了两次?
SELECTs.name,ds.nameFROMstudentASsJOINclassASdsONs.class_id=ds.idwhereds.id=1;我们明明知道这里只有4条数据,但是为什么出现了5条?
原因是:当游标执行到末尾时,再次移动会触发:1329报错,而我们此时处理方式是continue,让程序继续执行,此时游标就会指向最后一行的位置,执行完,然后退出循环
此时就出现了,最后一行的数据重复了一次;
当然如果我们想要合理的输出结果,我们需要使用LOOP循环来做
3.1 游标 + 条件处理程序完整可运行存储过程(逐行遍历表并退出~)
DELIMITER//CREATEPROCEDUREp7(INclass_idINT)BEGIN-- 创建我们所需要的变量-- 学生姓名DECLAREstudent_nameVARCHAR(20);-- 班级名字DECLAREclass_namevarchar(20);-- 创建判断条件DECLAREis_doneboolDEFAULTFALSE;-- 声明游标DECLAREs_cursorCURSORFORSELECTs.name,ds.nameFROMstudentASsJOINclassASdsONs.class_id=ds.idwhereds.id=class_id;-- 创建条件处理程序DECLARECONTINUEHANDLERFORNOTFOUNDSETis_done :=TRUE;-- 创建新表CREATETABLEIFNOTEXISTSt_student_class(idINTPRIMARYKEYAUTO_INCREMENT,student_nameVARCHAR(20),class_nameVARCHAR(20));-- 打开游标OPENs_cursor;read_loop:LOOP-- 获取FETCHs_cursorINTOstudent_name,class_name;IFis_doneTHENLEAVEread_loop;ENDIF;-- 插入数据INSERTINTOt_student_classVALUES(null,student_name,class_name);ENDLOOPread_loop;END//DELIMITER;此时的结果就符合我们预想中的效果了:
核心要点复盘
DECLARE顺序:普通变量 →HANDLER条件处理器 →CURSOR游标,顺序颠倒直接语法报错NOT FOUND就是 fetch 读完所有行的信号,不要捕获报错退出,用标记变量 +LEAVE跳出循环- 游标不要滥用,数据库是集合运算,尽量用 SQL 批量,少用逐行游标逻辑。