一、什么是游标?
游标(Cursor) 是MySQL中用于逐行处理查询结果集的数据库对象。它类似于指针,允许开发者在结果集中逐行移动,并对每一行数据进行特定操作。游标将传统的集合操作转换为面向过程的记录处理方式,特别适用于需要逐行逻辑判断或复杂计算的场景。
为什么需要游标?
-
逐行处理:如根据每行数据动态生成计算结果或触发业务逻辑。
-
复杂逻辑:需要基于当前行数据状态执行条件分支操作。
-
个性化操作:不同记录需要不同的处理策略(如生成定制化报告)。
二、游标的优缺点:权衡使用场景
优点
-
灵活性强:支持逐行数据访问与操作。
-
内存高效:分批处理大型结果集,避免一次性加载内存溢出。
-
过程化控制:可在循环中结合条件判断和变量计算。
缺点
-
性能开销:比集合操作(如JOIN、子查询)效率低。
-
资源消耗:占用数据库连接资源,长时间未关闭可能导致阻塞。
-
复杂度高:代码量增加,调试和维护难度大。
三、游标操作流程:五步掌握核心用法
1. 声明游标
DECLARE cursor_name CURSOR FOR
SELECT column1, column2 FROM table WHERE condition;
-
作用:定义游标名称和关联的查询,不执行查询。
2. 打开游标
OPEN cursor_name;
-
作用:执行关联的SELECT语句,生成结果集。
3. 获取数据
FETCH cursor_name INTO var1, var2;
-
作用:将当前行数据存入变量,游标下移一行。
4. 处理结束条件
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
-
作用:当FETCH无更多数据时触发,设置结束标志。
5. 关闭游标
CLOSE cursor_name;
-
作用:释放游标占用的资源,必须显式调用。
四、实战案例:游标典型应用场景
案例1:生成学生成绩评估报告
需求:为每个学生的每门课程生成等级和建议。
DELIMITER $$
CREATE PROCEDURE generate_grade_reports()
BEGINDECLARE v_sid INT;DECLARE v_score DECIMAL(5,2);DECLARE done INT DEFAULT 0;-- 声明游标:获取所有学生成绩DECLARE grade_cursor CURSOR FORSELECT sid, score FROM t_score;DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;OPEN grade_cursor;grade_loop: LOOPFETCH grade_cursor INTO v_sid, v_score;IF done THEN LEAVE grade_loop; END IF;-- 根据分数生成等级CASE WHEN v_score >= 90 THEN INSERT INTO t_report VALUES (v_sid, 'A', '优秀');WHEN v_score >= 80 THEN INSERT INTO t_report VALUES (v_sid, 'B', '良好');-- 更多条件...END CASE;END LOOP;CLOSE grade_cursor;
END $$
DELIMITER ;
案例2:统计学生总分与平均分
需求:逐学生计算总分、平均分并汇总。
DELIMITER $$
CREATE PROCEDURE calculate_student_stats()
BEGINDECLARE v_sid INT;DECLARE v_total, v_avg DECIMAL;DECLARE done INT DEFAULT 0;-- 声明游标:遍历学生DECLARE student_cursor CURSOR FORSELECT sid FROM t_student;DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;OPEN student_cursor;student_loop: LOOPFETCH student_cursor INTO v_sid;IF done THEN LEAVE student_loop; END IF;-- 计算当前学生的统计值SELECT SUM(score), AVG(score) INTO v_total, v_avgFROM t_score WHERE sid = v_sid;INSERT INTO t_stats VALUES (v_sid, v_total, v_avg);END LOOP;CLOSE student_cursor;
END $$
DELIMITER ;
案例3:批量调整课程成绩
需求:为某课程所有学生成绩增加固定分值,不超过100分。
DELIMITER $$
CREATE PROCEDURE adjust_scores(IN course_id INT, IN adjust DECIMAL)
BEGINDECLARE v_sid INT;DECLARE v_old_score DECIMAL;DECLARE done INT DEFAULT 0;-- 声明游标:获取指定课程成绩DECLARE score_cursor CURSOR FORSELECT sid, score FROM t_score WHERE cid = course_id;DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;OPEN score_cursor;adjust_loop: LOOPFETCH score_cursor INTO v_sid, v_old_score;IF done THEN LEAVE adjust_loop; END IF;-- 计算新成绩并更新UPDATE t_score SET score = LEAST(v_old_score + adjust, 100)WHERE sid = v_sid AND cid = course_id;END LOOP;CLOSE score_cursor;
END $$
DELIMITER ;
五、常见问题与解决方案
问题1:游标死循环
-
现象:存储过程无法退出循环。
-
原因:未正确处理
NOT FOUND
条件。 -
解决:确保声明
CONTINUE HANDLER
并设置终止标志。
问题2:游标性能低下
-
现象:处理速度慢,数据库负载高。
-
原因:循环内执行复杂查询或大量计算。
-
解决:预先计算中间结果,使用临时表存储数据。
问题3:资源泄漏
-
现象:连接数异常增长,数据库响应变慢。
-
原因:未显式关闭游标。
-
解决:在结束处理后务必执行
CLOSE
。
六、最佳实践与优化建议
-
限制使用场景:优先使用集合操作,仅在必须逐行处理时使用游标。
-
优化查询语句:游标关联的SELECT语句需高效,避免全表扫描。
-
批量提交事务:在循环内定期COMMIT,减少锁竞争。
-
资源及时释放:处理完成后立即关闭游标。
-
监控性能:使用
EXPLAIN
分析查询计划,检查执行时间。
七、总结
适用场景:
-
逐行数据校验(如格式检查)
-
动态生成报告(如个性化评价)
-
级联更新/删除(如历史数据迁移)
慎用场景:
-
大数据量处理(性能敏感)
-
高并发业务(资源竞争激烈)