一、什么是游标?
游标(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()
BEGIN
DECLARE v_sid INT;
DECLARE v_score DECIMAL(5,2);
DECLARE done INT DEFAULT 0;
-- 声明游标:获取所有学生成绩
DECLARE grade_cursor CURSOR FOR
SELECT sid, score FROM t_score;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN grade_cursor;
grade_loop: LOOP
FETCH 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()
BEGIN
DECLARE v_sid INT;
DECLARE v_total, v_avg DECIMAL;
DECLARE done INT DEFAULT 0;
-- 声明游标:遍历学生
DECLARE student_cursor CURSOR FOR
SELECT sid FROM t_student;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN student_cursor;
student_loop: LOOP
FETCH student_cursor INTO v_sid;
IF done THEN LEAVE student_loop; END IF;
-- 计算当前学生的统计值
SELECT SUM(score), AVG(score) INTO v_total, v_avg
FROM 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)
BEGIN
DECLARE v_sid INT;
DECLARE v_old_score DECIMAL;
DECLARE done INT DEFAULT 0;
-- 声明游标:获取指定课程成绩
DECLARE score_cursor CURSOR FOR
SELECT sid, score FROM t_score WHERE cid = course_id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN score_cursor;
adjust_loop: LOOP
FETCH 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
分析查询计划,检查执行时间。
七、总结
适用场景:
-
逐行数据校验(如格式检查)
-
动态生成报告(如个性化评价)
-
级联更新/删除(如历史数据迁移)
慎用场景:
-
大数据量处理(性能敏感)
-
高并发业务(资源竞争激烈)