游标(Cursor)是数据库系统中的一种对象,它提供了一种机制,使应用程序能够逐行访问SQL查询返回的结果集。游标充当一个指针,指向查询结果集中的当前行,允许应用程序按需对数据进行检索和操作。
游标是将集合处理方式转换为面向过程的记录处理方式的一种重要工具,特别适用于需要逐行处理的复杂业务逻辑实现。
在以下场景中,游标的使用尤为必要:
复杂数据处理:需要基于当前记录状态执行条件逻辑的情况
逐行操作:需要对结果集中的每一行执行特定操作时
个性化处理:针对不同记录属性需要采取不同处理策略的情况
统计与聚合:需要在过程中维护状态变量的复杂计算
示例场景:在学生成绩处理系统中,需要根据不同学生的成绩区间,给予不同的评价和建议。
优势:
提供了对数据的逐行操作能力,增强了灵活性
允许根据当前记录状态动态决定处理逻辑
简化了需要依赖前一记录内容的计算实现
为处理大型结果集提供了一种内存效率更高的方法
局限性:
性能开销较大,相比集合操作效率较低
增加了数据库服务器资源消耗
代码复杂度增加
潜在的并发问题和资源管理挑战
MySQL中的游标操作遵循以下规范化流程:
DECLARE cursor_name CURSOR FOR select_statement;
声明游标定义了一个结果集,但不执行查询。此语句指定了游标名称和返回游标结果集的SELECT语句。
OPEN cursor_name;
OPEN语句执行游标的SELECT查询,并使游标指向结果集中第一行之前的位置。
FETCH cursor_name INTO variable_list;
FETCH语句从游标当前位置检索下一行数据,并将其存储在指定的变量中,同时将游标移动到下一行。
CLOSE cursor_name;
CLOSE语句释放与游标相关的所有资源,但保留游标定义,以便后续可以再次打开。
DECLARE CONTINUE HANDLER FOR NOT FOUND SET variable = value;
为NOT FOUND条件定义处理程序,确保在游标遍历完所有记录后能够正常退出循环。
教务处需要计算每个学生的平均分和总分,用于排名和评优。
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:游标结果集被修改
原因:在游标循环中修改了游标查询的表
解决方案:避免在处理过程中修改游标基于的表结构或索引字段