一:游标概述
1、游标的概念
游标(Cursor)是数据库系统中的一种对象,它提供了一种机制,使应用程序能够逐行访问SQL查询返回的结果集。游标充当一个指针,指向查询结果集中的当前行,允许应用程序按需对数据进行检索和操作。
游标是将集合处理方式转换为面向过程的记录处理方式的一种重要工具,特别适用于需要逐行处理的复杂业务逻辑实现。
2、游标的必要性
在以下场景中,游标的使用尤为必要:
-
复杂数据处理:需要基于当前记录状态执行条件逻辑的情况
-
逐行操作:需要对结果集中的每一行执行特定操作时
-
个性化处理:针对不同记录属性需要采取不同处理策略的情况
-
统计与聚合:需要在过程中维护状态变量的复杂计算
示例场景:在学生成绩处理系统中,需要根据不同学生的成绩区间,给予不同的评价和建议。
3、游标的优缺点分析
优势:
-
提供了对数据的逐行操作能力,增强了灵活性
-
允许根据当前记录状态动态决定处理逻辑
-
简化了需要依赖前一记录内容的计算实现
-
为处理大型结果集提供了一种内存效率更高的方法
局限性:
-
性能开销较大,相比集合操作效率较低
-
增加了数据库服务器资源消耗
-
代码复杂度增加
-
潜在的并发问题和资源管理挑战
二:游标的操作流程
MySQL中的游标操作遵循以下规范化流程:
1、游标声明
DECLARE cursor_name CURSOR FOR select_statement;
声明游标定义了一个结果集,但不执行查询。此语句指定了游标名称和返回游标结果集的SELECT语句。
2、游标打开操作
OPEN cursor_name;
OPEN语句执行游标的SELECT查询,并使游标指向结果集中第一行之前的位置。
3、数据获取操作
FETCH cursor_name INTO variable_list;
FETCH语句从游标当前位置检索下一行数据,并将其存储在指定的变量中,同时将游标移动到下一行。
4、游标关闭操作
CLOSE cursor_name;
CLOSE语句释放与游标相关的所有资源,但保留游标定义,以便后续可以再次打开。
5、处理游标结束情况
DECLARE CONTINUE HANDLER FOR NOT FOUND SET variable = value;
为NOT FOUND条件定义处理程序,确保在游标遍历完所有记录后能够正常退出循环。
列:
-- 创建一个存储过程,希望将所有的学生的分数加起来
DROP PROCEDURE if EXISTS get_sum;
delimiter $$
CREATE PROCEDURE get_sum()
BEGIN
-- 定义获取游标数据的字段
DECLARE a1 INT;
DECLARE a2 INT;
DECLARE a3 DOUBLE;
DECLARE p INT DEFAULT 0;
-- 定义游标
DECLARE c1 CURSOR FOR
SELECT sid,cid,score FROM t_score;
-- 异常捕获
DECLARE CONTINUE HANDLER FOR NOT FOUND SET p = 1;
-- 游标打开
OPEN c1;
myloop: LOOP
-- 数据抓取
FETCH c1 INTO a1,a2,a3;
if p =1 THEN
LEAVE myloop;
end IF;
SELECT a1,a2,a3;
END LOOP;
-- 关闭游标
CLOSE c1;
END $$
delimiter ;
CALL get_sum();
三:学生成绩统计分析
教务处需要计算每个学生的平均分和总分,用于排名和评优。
CREATE TABLE `t_student_summary` (
`summary_id` int NOT NULL AUTO_INCREMENT,
`sid` int NOT NULL COMMENT '学生编号',
`sname` varchar(50) NOT NULL COMMENT '学生姓名',
`total_score` decimal(8,2) NOT NULL COMMENT '总分',
`avg_score` decimal(5,2) NOT NULL COMMENT '平均分',
`course_count` int NOT NULL COMMENT '课程数量',
`create_time` datetime NOT NULL,
PRIMARY KEY (`summary_id`)
);
实现存储过程:
DELIMITER $$
CREATE PROCEDURE calculate_student_scores()
BEGIN
-- 声明变量
DECLARE v_sid INT;
DECLARE v_sname VARCHAR(50);
DECLARE v_ssex CHAR(1);
DECLARE v_total DECIMAL(8,2);
DECLARE v_avg DECIMAL(5,2);
DECLARE v_count INT;
DECLARE done INT DEFAULT 0;
-- 声明游标 - 获取所有学生信息
DECLARE student_cursor CURSOR FOR
SELECT sid, sname, ssex FROM t_student ORDER BY sid;
-- 设置结束标志
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
-- 清空统计表
TRUNCATE TABLE t_student_summary;
-- 打开游标
OPEN student_cursor;
-- 开始循环处理每个学生
student_loop: LOOP
-- 获取当前学生
FETCH student_cursor INTO v_sid, v_sname, v_ssex;
-- 检查是否结束
IF done THEN
LEAVE student_loop;
END IF;
-- 计算该学生的成绩统计
SELECT IFNULL(SUM(score), 0), IFNULL(AVG(score), 0), COUNT(*)
INTO v_total, v_avg, v_count
FROM t_score
WHERE sid = v_sid;
-- 写入统计表
INSERT INTO t_student_summary (sid, sname, total_score, avg_score, course_count, create_time)
VALUES (v_sid, v_sname, v_total, v_avg, v_count, NOW());
END LOOP student_loop;
-- 关闭游标
CLOSE student_cursor;
-- 显示统计结果,按平均分降序排列
SELECT * FROM t_student_summary ORDER BY avg_score DESC;
END $$
DELIMITER ;
四、成绩批量调整处理
发现评分标准有误差,需要对特定课程的所有学生成绩进行统一调整。
DELIMITER $$
CREATE PROCEDURE adjust_course_scores(IN p_cid INT, IN p_adjustment DECIMAL(5,2))
BEGIN
-- 声明变量
DECLARE v_sid INT;
DECLARE v_current_score DECIMAL(5,2);
DECLARE v_new_score DECIMAL(5,2);
DECLARE done INT DEFAULT 0;
-- 声明游标 - 获取指定课程的所有学生成绩
DECLARE score_cursor CURSOR FOR
SELECT sid, score
FROM t_score
WHERE cid = p_cid;
-- 设置结束标志
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
-- 创建临时表记录调整历史
CREATE TEMPORARY TABLE IF NOT EXISTS temp_score_adjustments (
sid INT,
old_score DECIMAL(5,2),
new_score DECIMAL(5,2)
);
-- 打开游标
OPEN score_cursor;
-- 开始循环处理每个学生成绩
score_loop: LOOP
-- 获取当前学生成绩
FETCH score_cursor INTO v_sid, v_current_score;
-- 检查是否结束
IF done THEN
LEAVE score_loop;
END IF;
-- 计算新成绩(加上调整分数,但不超过100分)
SET v_new_score = LEAST(v_current_score + p_adjustment, 100);
-- 更新成绩
UPDATE t_score
SET score = v_new_score
WHERE sid = v_sid AND cid = p_cid;
-- 记录调整历史
INSERT INTO temp_score_adjustments (sid, old_score, new_score)
VALUES (v_sid, v_current_score, v_new_score);
END LOOP score_loop;
-- 关闭游标
CLOSE score_cursor;
-- 删除临时表
END $$
DELIMITER ;
五、游标常见问题
问题1:游标处理死循环
-
原因:未正确设置NOT FOUND处理程序
-
解决方案:确保声明CONTINUE HANDLER FOR NOT FOUND并正确设置循环终止条件
问题2:资源泄漏
-
原因:未关闭游标
-
解决方案:在处理完成后总是执行CLOSE语句释放资源
问题3:性能低下
-
原因:游标循环中执行了过多复杂查询
-
解决方案:优化查询,预先计算或使用临时表存储中间结果
问题4:游标结果集被修改
-
原因:在游标循环中修改了游标查询的表
-
解决方案:避免在处理过程中修改游标基于的表结构或索引字段