乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 数据库基础与安装

    • MySQL 简介
    • MySQL 5.6 到 5.7 到 8.0 关键差异速查
    • 安装 MySQL 5.7
    • 连接与断开服务器
    • 创建数据库
    • 创建数据表
    • 数据库与数据表
    • 加载数据
    • 获取数据库信息
    • 批处理模式
    • SHOW 语句汇总
    • FLUSH 与 RESET 语句
    • my.cnf 核心参数
    • 字符集与排序规则
  • 第2章 SQL基础查询

    • SELECT
    • WHERE
    • ORDER BY
    • LIMIT
    • COUNT
    • 聚合函数
    • 比较运算符
    • 逻辑运算符
    • 算术运算符
    • 模式匹配
    • NULL 值处理
    • UPDATE
    • DELETE
    • REPLACE
    • SELECT INTO
  • 第3章 数据类型与运算符

    • 数值类型
    • 字符串类型
    • 日期时间类型
    • BIT 类型
    • ENUM 类型
    • SET 类型
    • JSON 类型
    • 类型转换
  • 第4章 函数与表达式

    • 字符串函数
    • 数值函数
    • 日期函数
    • 全文检索函数
  • 第5章 高级查询与子查询

    • JOIN
    • 子查询
    • UNION
    • GROUP BY
    • HAVING
    • DISTINCT
  • 第6章 表与索引

    • 数据定义语言
    • 修改表结构
    • 视图
    • 修改视图与检查选项
    • 外键
    • 索引
    • 唯一索引
    • 复合索引
    • 存储引擎对比
    • 分区表
    • 第一范式与第二范式
    • 第三范式与 BC 范式
    • 反范式设计
  • 第7章 存储过程与函数

    • 存储过程
    • 存储函数
    • 变量
    • 流程控制
    • 游标
    • 预处理语句
  • 第8章 事务与锁

    • 事务
    • 事务隔离级别
    • 锁机制
    • MVCC
    • 死锁专题分析
    • LOCK TABLES
    • XA 事务
  • 第9章 用户管理与安全

    • 用户管理
    • 权限管理
    • 角色
    • SQL 注入防范
  • 第10章 性能优化入门

    • 执行计划
    • 索引优化
    • 查询优化
    • 查询优化器提示
    • 慢查询日志
    • InnoDB 深入机制
    • InnoDB 专项优化
    • Performance Schema
    • sys Schema
  • 第11章 复制与高可用

    • 主从复制原理
    • 半同步复制配置
    • binlog 开启与 point-in-time 恢复
    • mysqldump 全库备份
    • mysqldump 单表与条件备份
    • mysqldump 恢复与导入
    • xtrabackup 全量热备
    • xtrabackup 准备与恢复
    • xtrabackup 增量与流式备份
  • 第12章 触发器与事件

    • 触发器
    • 事件调度器
  • 参考

    • MySQL 5.7 专业术语大全
    • MySQL 5.7 关键字与保留字大全

游标

导学

当需要在存储过程中逐行处理查询结果时,游标(Cursor)是唯一的选择。它像文件指针一样,让你可以一行一行地遍历结果集。

定义

游标(Cursor):一种数据库对象,用于在存储过程或函数中逐行遍历 SELECT 查询的结果集。游标允许对每一行进行单独处理。

核心语法

DECLARE cursor_name CURSOR FOR SELECT_statement;
OPEN cursor_name;
FETCH cursor_name INTO var1, var2, ...;
CLOSE cursor_name;

完整示例一:遍历员工并累加成绩

当前数据状态

首先建立示例表并插入数据:

CREATE DATABASE IF NOT EXISTS school;
USE school;

DROP TABLE IF EXISTS employees;
CREATE TABLE employees (
    emp_id INT PRIMARY KEY AUTO_INCREMENT,
    emp_name VARCHAR(20),
    dept VARCHAR(20),
    score DECIMAL(5,2)
);

INSERT INTO employees (emp_name, dept, score) VALUES
('大翔', '技术部', 100),
('白歌', '技术部', NULL);

当前 employees 表数据:

emp_idemp_namedeptscore
1大翔技术部100
2白歌技术部NULL

操作语句

创建使用游标累加成绩的存储过程:

DELIMITER //

CREATE PROCEDURE CalcTotalScore()
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE v_score DECIMAL(5,2);
    DECLARE v_total DECIMAL(10,2) DEFAULT 0;

    -- 1. 声明游标
    DECLARE cur CURSOR FOR SELECT score FROM employees;

    -- 2. 声明继续处理器:当没有更多行时设置 done = TRUE
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

    -- 3. 打开游标
    OPEN cur;

    -- 4. 循环读取
    read_loop: LOOP
        FETCH cur INTO v_score;
        IF done THEN
            LEAVE read_loop;
        END IF;
        IF v_score IS NOT NULL THEN
            SET v_total = v_total + v_score;
        END IF;
    END LOOP;

    -- 5. 关闭游标
    CLOSE cur;

    SELECT v_total AS 总分;
END //

DELIMITER ;

-- 调用
CALL CalcTotalScore();

操作后的结果

返回:

总分
100

结果解读

执行步骤拆解:

步骤操作v_scorev_total
初始初始化变量NULL0
第1次FETCH读取第1行100100
第2次FETCH读取第2行NULL100(NULL不加入)
第3次FETCH无更多行,触发 NOT FOUNDNULL100
  • DECLARE cur CURSOR FOR SELECT score FROM employees; 声明游标,关联查询语句
  • DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; 定义处理器:当游标没有更多行时,自动设置 done = TRUE
  • OPEN cur; 打开游标,执行关联的查询
  • FETCH cur INTO v_score; 读取当前行数据到变量
  • 当 FETCH 不到数据时,NOT FOUND 处理器触发,done 变为 TRUE
  • CLOSE cur; 关闭游标,释放资源

完整示例二:带条件的游标遍历

当前数据状态

基于 employees 表:

emp_idemp_namedeptscore
1大翔技术部100
2白歌技术部NULL

操作语句

创建使用游标分类处理的存储过程:

DELIMITER //

CREATE PROCEDURE CategorizeEmployees()
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE v_id INT;
    DECLARE v_name VARCHAR(20);
    DECLARE v_score DECIMAL(5,2);

    DECLARE cur CURSOR FOR
        SELECT emp_id, emp_name, score FROM employees;

    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

    -- 创建临时表存储分类结果
    CREATE TEMPORARY TABLE IF NOT EXISTS temp_category (
        emp_id INT,
        emp_name VARCHAR(20),
        category VARCHAR(10)
    );
    TRUNCATE TABLE temp_category;

    OPEN cur;

    read_loop: LOOP
        FETCH cur INTO v_id, v_name, v_score;
        IF done THEN
            LEAVE read_loop;
        END IF;

        IF v_score >= 90 THEN
            INSERT INTO temp_category VALUES (v_id, v_name, '优秀');
        ELSEIF v_score IS NULL THEN
            INSERT INTO temp_category VALUES (v_id, v_name, '未评分');
        ELSE
            INSERT INTO temp_category VALUES (v_id, v_name, '普通');
        END IF;
    END LOOP;

    CLOSE cur;

    SELECT * FROM temp_category;
    DROP TEMPORARY TABLE IF EXISTS temp_category;
END //

DELIMITER ;

-- 执行分类
CALL CategorizeEmployees();

操作后的结果

返回:

emp_idemp_namecategory
1大翔优秀
2白歌未评分

结果解读

  • 游标遍历 employees 表,每行包含 emp_id、emp_name 和 score
  • 根据 score 值将分类结果写入临时表
  • 100分及以上为优秀,NULL 为未评分,其余为普通

游标 vs 集合操作

完整示例:同样的逻辑用集合操作实现

当前数据状态

基于 employees 表:

emp_idemp_namedeptscore
1大翔技术部100
2白歌技术部NULL

操作语句

用一条 INSERT ... SELECT 替代游标逐行处理:

-- 创建临时表存储分类结果
CREATE TEMPORARY TABLE IF NOT EXISTS temp_category_set (
    emp_id INT,
    emp_name VARCHAR(20),
    category VARCHAR(10)
);
TRUNCATE TABLE temp_category_set;

-- 用集合操作一次性完成分类(替代游标逐行 FETCH + INSERT)
INSERT INTO temp_category_set
SELECT emp_id, emp_name,
    CASE
        WHEN score >= 90 THEN '优秀'
        WHEN score IS NULL THEN '未评分'
        ELSE '普通'
    END
FROM employees;

-- 查看结果
SELECT * FROM temp_category_set;
DROP TEMPORARY TABLE IF EXISTS temp_category_set;

操作后的结果

emp_idemp_namecategory
1大翔优秀
2白歌未评分

结果解读

场景游标方式集合方式(推荐)
累加逐行 FETCH + 累加SELECT SUM(score)
条件分类逐行判断 + INSERTINSERT ... SELECT ... CASE WHEN ...
批量更新逐行 UPDATEUPDATE ... CASE WHEN ...
  • 集合操作利用数据库引擎的优化能力,通常比游标快得多
  • 游标逐行处理,增加了网络/CPU 开销
  • 能用一条 SQL 解决的,绝不要用游标

完整示例三:游标遍历多列数据

当前数据状态

基于 employees 表:

emp_idemp_namedeptscore
1大翔技术部100
2白歌技术部NULL

操作语句

创建使用游标处理多列的存储过程:

DELIMITER //

CREATE PROCEDURE GetEmployeeReport()
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE v_name VARCHAR(20);
    DECLARE v_dept VARCHAR(20);
    DECLARE v_score DECIMAL(5,2);

    -- 创建临时表存储结果
    CREATE TEMPORARY TABLE IF NOT EXISTS temp_report (
        emp_name VARCHAR(20),
        dept VARCHAR(20),
        score DECIMAL(5,2),
        remark VARCHAR(20)
    );
    TRUNCATE TABLE temp_report;

    DECLARE cur CURSOR FOR
        SELECT emp_name, dept, score FROM employees ORDER BY score DESC;

    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

    OPEN cur;

    read_loop: LOOP
        FETCH cur INTO v_name, v_dept, v_score;
        IF done THEN
            LEAVE read_loop;
        END IF;

        -- 根据分数添加评语
        IF v_score >= 90 THEN
            INSERT INTO temp_report VALUES (v_name, v_dept, v_score, '优秀');
        ELSEIF v_score IS NULL THEN
            INSERT INTO temp_report VALUES (v_name, v_dept, v_score, '未评分');
        ELSE
            INSERT INTO temp_report VALUES (v_name, v_dept, v_score, '普通');
        END IF;
    END LOOP;

    CLOSE cur;

    SELECT * FROM temp_report;
    DROP TEMPORARY TABLE IF EXISTS temp_report;
END //

DELIMITER ;

-- 调用
CALL GetEmployeeReport();

操作后的结果

返回:

emp_namedeptscoreremark
大翔技术部100优秀
白歌技术部NULL未评分

结果解读

  • 游标 FETCH 多列数据时,INTO 子句的变量顺序和 SELECT 列的顺序必须一致
  • 本例展示了游标在逐行处理时,可以结合流程控制做复杂逻辑
  • 结果按分数从高到低排列,并标注了评语

常见误区

误区正解
游标比集合操作快恰恰相反。游标逐行处理,网络/CPU 开销大。集合操作是数据库的强项。
游标可以向前向后移动MySQL 5.7 的游标只能向前(FETCH NEXT),不能向后或随机访问。
不关闭游标没关系游标占用服务器资源,必须在结束时关闭。

面试考点

Q:游标的优缺点?

优点:可以逐行处理结果集,适合复杂逐行逻辑。缺点:性能差,逐行处理违背了 SQL 的集合操作思想。能用集合操作替代的不要用游标。

Q:MySQL 游标有什么限制?

  1. 只读,不能通过游标修改数据;2. 只能向前移动(FETCH NEXT);3. 一次只能打开一个游标进行 FETCH(在存储过程中);4. 游标必须在 DECLARE 变量之后、HANDLER 之前声明。

Q:NOT FOUND Handler 的作用?

当 FETCH 没有更多行时,MySQL 会抛出一个 NOT FOUND 条件。DECLARE CONTINUE HANDLER FOR NOT FOUND 捕获这个条件,设置标志变量,让循环正常退出而不是报错。

小结

  • 游标用于在存储过程中逐行遍历结果集
  • 使用步骤:DECLARE → OPEN → FETCH → CLOSE
  • NOT FOUND Handler 用于检测结果集结束
  • 游标性能差,能用集合操作(JOIN、子查询、CASE)替代的不要用游标

下一章引子:存储过程和函数是主动调用的程序单元,而触发器则是被动触发的——当表发生 INSERT、UPDATE、DELETE 时自动执行。

上一页
流程控制
下一页
预处理语句