游标-数据库中的指针/迭代器

一、核心概念:游标是什么?

你可以把游标理解为数据库中的"指针/迭代器"

  • 普通的SELECT语句会一次性返回整个结果集(比如SELECT * FROM users会查出所有用户),而游标可以让你逐行读取这个结果集,对每一行数据进行单独的处理(比如判断、计算、更新等)。
  • 形象比喻:游标就像你看书时的书签,一次只定位到一行,处理完这一行再移动到下一行,直到遍历完所有行。

游标的核心特点

  1. 面向单行:一次只处理结果集中的一行数据;
  2. 可遍历:支持从第一行移动到最后一行(单向遍历,MySQL仅支持向前遍历);
  3. 常用于存储过程/函数:几乎不会单独使用,通常嵌套在存储过程/函数中处理复杂的行级逻辑。

二、为什么需要游标?

当你需要对查询结果进行逐行的个性化处理 ,而普通SQL语句(如UPDATEINSERT)无法满足时,游标就派上用场了。比如:

  • 遍历订单表,对每个用户的订单金额超过1000的订单自动打9折;
  • 统计每个部门的员工薪资,对薪资低于平均值的员工生成提醒;
  • 批量处理数据时,逐行校验数据合法性,不合法的记录单独记录日志。

三、游标使用步骤(MySQL实战)

游标使用遵循固定的"五步流程",下面结合一个完整示例(遍历用户表,给年龄<18的用户标记为"未成年")讲解:

完整示例代码

sql 复制代码
-- 创建存储过程:使用游标标记未成年用户
DELIMITER //
CREATE PROCEDURE MarkMinorUsers()
BEGIN
    -- 1. 声明变量:存储游标读取的每行数据
    DECLARE user_id INT;
    DECLARE user_age INT;
    -- 声明结束标志变量(必须是INT,0/1表示是否结束)
    DECLARE done INT DEFAULT 0;

    -- 2. 声明游标:关联查询结果集
    DECLARE user_cursor CURSOR FOR 
        SELECT id, age FROM users;  -- 游标关联"用户ID+年龄"的结果集

    -- 3. 声明异常处理:当游标遍历完所有行时,设置done=1
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

    -- 4. 打开游标:执行关联的SELECT语句,加载结果集到内存
    OPEN user_cursor;

    -- 5. 遍历游标:逐行读取并处理数据
    read_loop: LOOP  -- 定义循环标签read_loop
        -- 读取当前行数据到变量(顺序要和游标SELECT的列一致)
        FETCH user_cursor INTO user_id, user_age;

        -- 判断是否遍历完成,完成则退出循环
        IF done = 1 THEN
            LEAVE read_loop;
        END IF;

        -- 核心业务逻辑:判断年龄,标记未成年
        IF user_age < 18 THEN
            UPDATE users SET status = '未成年' WHERE id = user_id;
        END IF;
    END LOOP read_loop;

    -- 6. 关闭游标:释放内存
    CLOSE user_cursor;

    -- 提示执行完成
    SELECT '已完成未成年用户标记' AS result;
END //
DELIMITER ;

-- 调用存储过程
CALL MarkMinorUsers();

关键步骤拆解

步骤 语法/操作 作用
1. 声明变量 DECLARE 变量名 类型; 存储游标逐行读取的数据(如用户ID、年龄),并声明done变量标记遍历结束
2. 声明游标 DECLARE 游标名 CURSOR FOR SELECT语句; 把游标和一个查询结果集绑定(告诉游标要遍历哪些数据)
3. 异常处理 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1; 游标遍历到最后一行后,MySQL会抛出"NOT FOUND"异常,这里捕获并设置done=1,避免报错
4. 打开游标 OPEN 游标名; 执行游标绑定的SELECT语句,把结果集加载到内存,准备遍历
5. 遍历游标 FETCH 游标名 INTO 变量1, 变量2; 读取当前行数据到变量,结合循环逐行处理;LEAVE用于退出循环
6. 关闭游标 CLOSE 游标名; 释放游标占用的内存(必须关闭,否则会造成资源泄漏)

四、使用游标注意事项(新手避坑)

  1. 性能问题 :游标是逐行处理数据,效率远低于批量SQL操作(如UPDATE users SET status='未成年' WHERE age<18),能不用就不用,仅在批量操作无法满足复杂逻辑时使用。
  2. 资源占用 :打开游标后会占用数据库连接和内存,必须在遍历完成后CLOSE,否则会导致连接泄漏。
  3. 只读特性 :MySQL的游标默认是"只读"的,无法通过游标直接修改当前行的数据(需通过UPDATE语句单独修改)。
  4. 单向遍历:MySQL仅支持"向前遍历",不能回退到上一行,也不能跳行遍历。
  5. 事务影响 :游标遍历过程中如果执行UPDATE/DELETE,会影响事务,建议在存储过程中明确控制事务(START TRANSACTION/COMMIT/ROLLBACK)。

五、游标 vs 批量SQL(新手怎么选?)

场景 推荐方案 原因
简单条件的批量更新/删除 批量SQL(如UPDATE ... WHERE ... 效率高,语法简单
逐行复杂判断(如多表关联校验、动态计算) 游标 能处理单行的个性化逻辑
大数据量(10万+行) 避免游标 游标逐行处理会导致数据库卡顿,建议分批处理或用ETL工具

总结

  1. 游标本质:数据库中的"行级迭代器",用于逐行读取和处理查询结果集,通常嵌套在存储过程中使用。
  2. 核心流程:声明变量→声明游标→声明异常处理→打开游标→遍历处理→关闭游标(六步必记)。
  3. 使用原则:优先用批量SQL,仅在复杂行级逻辑无法用批量操作实现时才用游标,且注意及时关闭释放资源。

游标(Cursor) 和循环(Loop) 的核心区别

游标(Cursor)循环(Loop) 的核心区别,这是新手很容易混淆的两个概念------简单来说,游标是"遍历数据的工具",而循环是"重复执行代码的逻辑结构",二者常配合使用,但本质完全不同。


一、核心区别:先看本质

维度 游标 (Cursor) 循环 (Loop)
本质 数据库专属的数据遍历工具(指针) 通用的代码重复执行逻辑(流程控制)
核心作用 逐行读取查询结果集中的数据(定位到某一行) 让一段代码重复执行N次,或直到满足条件为止
依赖关系 必须基于SELECT结果集,无法独立遍历"空" 可以独立运行(比如循环打印1-10),不依赖数据
适用范围 仅数据库(存储过程/函数中) 所有编程语言(Python/Java/MySQL等)都有
是否可独立使用 不能(必须嵌套在循环里才能遍历完所有行) 能(比如LOOP+LEAVE实现简单循环)

形象比喻

  • 游标:像你手里的"购物清单",清单上列着所有要处理的商品(对应数据库结果集),游标帮你"指到当前要处理的那一项";
  • 循环:像你"重复拿清单→处理商品→看下一项"的动作,直到清单全部处理完。

二、单独使用示例:看清各自的作用

1. 仅用Loop(无游标):纯逻辑循环

比如在MySQL存储过程中,用Loop循环打印1-5(无任何数据遍历):

sql 复制代码
DELIMITER //
CREATE PROCEDURE SimpleLoopDemo()
BEGIN
    DECLARE num INT DEFAULT 1;  -- 初始化变量
    
    my_loop: LOOP
        -- 打印当前数字
        SELECT num AS current_number;
        
        -- 数字+1
        SET num = num + 1;
        
        -- 条件满足则退出循环
        IF num > 5 THEN
            LEAVE my_loop;
        END IF;
    END LOOP my_loop;
END //
DELIMITER ;

CALL SimpleLoopDemo();  -- 输出1、2、3、4、5

这个例子里没有游标,Loop只是单纯重复执行"打印+数字+1"的逻辑,和数据库表数据无关。

2. 游标必须配合Loop:否则只能读1行

如果只开游标、不写循环,游标只能读取结果集的第一行,无法遍历所有行:

sql 复制代码
DELIMITER //
CREATE PROCEDURE CursorWithoutLoop()
BEGIN
    DECLARE user_name VARCHAR(50);
    -- 声明游标(关联用户表)
    DECLARE name_cursor CURSOR FOR SELECT name FROM users LIMIT 3;
    
    OPEN name_cursor;
    -- 仅读取1行
    FETCH name_cursor INTO user_name;
    SELECT user_name;  -- 仅输出第一个用户名
    CLOSE name_cursor;
END //
DELIMITER ;

CALL CursorWithoutLoop();  -- 只能拿到第一行数据

这个例子说明:游标本身只能"定位一行",要遍历所有行,必须靠Loop重复执行FETCH

三、经典配合场景:游标+Loop遍历所有数据

这是最常见的用法------Loop提供"重复执行"的逻辑,游标提供"逐行取数据"的能力,结合起来完成批量行处理:

sql 复制代码
DELIMITER //
CREATE PROCEDURE CursorWithLoop()
BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE user_name VARCHAR(50);
    
    -- 1. 声明游标(绑定数据)
    DECLARE name_cursor CURSOR FOR SELECT name FROM users LIMIT 3;
    -- 2. 异常处理
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    
    OPEN name_cursor;
    
    -- 3. Loop提供循环逻辑
    read_loop: LOOP
        -- 游标逐行取数据
        FETCH name_cursor INTO user_name;
        
        -- 循环终止条件(游标遍历完则退出)
        IF done = 1 THEN
            LEAVE read_loop;
        END IF;
        
        -- 处理每行数据(这里仅打印)
        SELECT CONCAT('当前处理用户:', user_name) AS result;
    END LOOP read_loop;
    
    CLOSE name_cursor;
END //
DELIMITER ;

CALL CursorWithLoop();  -- 遍历输出3个用户名

关键分工

  • Loop:负责"重复执行→判断是否退出",是"循环的骨架";
  • 游标:负责"每次循环时取一行数据",是"循环的数据源";
  • 二者缺一不可:没有Loop,游标只能取1行;没有游标,Loop只是空转(无数据可处理)。

四、补充:MySQL中的其他循环(不止LOOP)

MySQL里除了LOOP,还有WHILEREPEAT循环,都可以和游标配合,本质逻辑一致:

sql 复制代码
-- 示例:WHILE循环+游标
DELIMITER //
CREATE PROCEDURE CursorWithWhile()
BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE user_age INT;
    DECLARE age_cursor CURSOR FOR SELECT age FROM users;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    
    OPEN age_cursor;
    -- WHILE循环
    WHILE done = 0 DO
        FETCH age_cursor INTO user_age;
        IF done = 0 THEN
            SELECT user_age;
        END IF;
    END WHILE;
    
    CLOSE age_cursor;
END //
DELIMITER ;

不管用哪种循环,核心都是:循环提供"重复执行"的逻辑,游标提供"逐行取数据"的能力


总结

  1. 核心区别:游标是"数据遍历工具"(针对数据库结果集),循环是"代码重复执行逻辑"(通用流程控制);
  2. 配合关系:游标必须依赖循环才能遍历完所有行,循环可以脱离游标独立执行(如纯数字循环);
  3. 使用场景:当需要逐行处理数据库结果集时,用「游标+循环」组合;仅需重复执行代码(无数据遍历)时,只用循环即可。
相关推荐
小高不会迪斯科7 小时前
CMU 15445学习心得(二) 内存管理及数据移动--数据库系统如何玩转内存
数据库·oracle
e***8907 小时前
MySQL 8.0版本JDBC驱动Jar包
数据库·mysql·jar
l1t7 小时前
在wsl的python 3.14.3容器中使用databend包
开发语言·数据库·python·databend
失忆爆表症9 小时前
03_数据库配置指南:PostgreSQL 17 + pgvector 向量存储
数据库·postgresql
AI_56789 小时前
Excel数据透视表提速:Power Query预处理百万数据
数据库·excel
SQL必知必会10 小时前
SQL 窗口帧:ROWS vs RANGE 深度解析
数据库·sql·性能优化
Gauss松鼠会10 小时前
【GaussDB】GaussDB数据库开发设计之JDBC高可用性
数据库·数据库开发·gaussdb
+VX:Fegn089510 小时前
计算机毕业设计|基于springboot + vue鲜花商城系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
识君啊11 小时前
SpringBoot 事务管理解析 - @Transactional 的正确用法与常见坑
java·数据库·spring boot·后端
一个天蝎座 白勺 程序猿11 小时前
破译JSON密码:KingbaseES全场景JSON数据处理实战指南
数据库·sql·json·kingbasees·金仓数据库