SQL 更多知识:使用游标

游标(Cursor)是处理单行数据的工具,尤其适用于需要逐行处理查询结果集的场景。与 SELECT 语句一次性返回所有结果不同,游标允许我们像操作指针一样,逐条读取、修改结果集中的数据,是数据库编程中不可或缺的技能。本文将围绕游标的创建、使用、关闭等核心操作展开,带你全面掌握游标的使用方法。

 

什么是游标?

游标是数据库系统提供的一种逐行处理查询结果集的机制,本质上是一个指向结果集的 “指针”。它打破了 SQL “集合操作” 的特性,允许以 “面向行” 的方式处理数据,常用于以下场景:

  • 逐行遍历复杂的查询结果,进行个性化处理;

  • 对查询结果中的每行数据执行条件更新、删除;

  • 将查询结果逐行输出或传递给应用程序。

注意:游标会占用数据库资源,且逐行处理效率低于集合操作,因此仅在必要时使用。

 

创建游标

创建游标是使用游标的第一步,不同数据库(如 MySQL、SQL Server、Oracle)的语法略有差异。大体上创建步骤如下:

(1)定义游标关联的查询语句

(2)再指定游标属性(如是否只读、是否可更新)。

通用语法如下:

DECLARE 游标名称 CURSOR [LOCAL|GLOBAL] [FORWARD_ONLY|SCROLL] 
[STATIC|KEYSET|DYNAMIC|FAST_FORWARD] [READ_ONLY|SCROLL_LOCKS|OPTIMISTIC]
FOR 
    -- 游标关联的SELECT查询语句(需返回结果集)
    SELECT 列1, 列2 FROM 表名 WHERE 条件;

关键参数说明

  • LOCAL/GLOBAL:游标作用域(局部 / 全局),LOCAL 仅在当前会话 / 存储过程中有效,GLOBAL 可跨会话使用;

  • FORWARD_ONLY/SCROLL:游标移动方向,FORWARD_ONLY 仅能向下(从第一行到最后一行),SCROLL 支持任意方向(如向上、跳行);

  • READ_ONLY:游标只读,无法通过游标修改数据;

  • FOR 子句:指定游标要遍历的查询结果集,这是游标的核心关联内容。

 

实战示例

下面示例以 MySQL 为例,是上面创建的 user 表,如下:

-- 创建遍历用户的游标
DECLARE user_cursor CURSOR FOR
    SELECT user_id,name,gender,phone,email,address FROM user WHERE gender = '男';

 

使用游标

创建游标后,需经过 “打开游标 → 读取数据 → 处理数据” 三步才能完成核心操作,这是游标的核心使用阶段。

(1)打开游标

打开游标会执行关联的 SELECT 语句,生成结果集并将游标指针定位到结果集的起始位置(第一行之前)。

语法如下:

OPEN 游标名称;

示例:

-- 打开上文创建的 user_cursor 游标
OPEN user_cursor;

(2)读取游标数据

读取数据是游标使用的核心,通过FETCH语句将游标指针移动到指定行,并将该行数据赋值给变量(需提前声明)。

基本语法:

FETCH [NEXT|PRIOR|FIRST|LAST] FROM 游标名称 INTO 变量1, 变量2, ...;

参数说明:

  • NEXT:读取下一行(最常用,默认值);

  • PRIOR:读取上一行(仅 SCROLL 游标支持);

  • FIRST/LAST:读取第一行 / 最后一行(仅 SCROLL 游标支持);

  • INTO:将读取的列数据赋值给对应的变量,变量数量、类型需与 SELECT 结果列一致。

 

完整使用示例(MySQL)

MySQL 中使用游标需结合存储过程(单独执行游标语句会报错):

-- 如果存储过程已存在则删除(避免重复创建报错)
DROP PROCEDURE IF EXISTS process_user_cursor;

-- 创建存储过程,使用游标遍历研发部员工并打印薪资
DELIMITER // -- 修改语句结束符为//,避免与存储过程内的;冲突
CREATE PROCEDURE process_user_cursor()
BEGIN
    -- 声明变量存储游标数据
    DECLARE v_user_id INT;
    DECLARE v_name VARCHAR(50);
    DECLARE done INT DEFAULT FALSE; -- 标记游标是否遍历完成
    
    -- 1. 创建游标
    DECLARE user_cursor CURSOR FOR
        SELECT user_id,name FROM user WHERE gender = '男';
    
    -- 声明游标结束处理程序,避免游标遍历完后报错
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    
    -- 2. 打开游标
    OPEN user_cursor;
    
    -- 3. 循环读取游标数据
    read_loop: LOOP
        -- 读取下一行数据到变量
        FETCH user_cursor INTO v_user_id, v_name;
        
        -- 判断是否遍历完成,完成则退出循环
        IF done THEN
            LEAVE read_loop;
        END IF;
        
        -- 处理数据:打印员工ID、姓名、薪资(MySQL中用SELECT模拟打印)
        SELECT CONCAT('用户ID:', v_user_id, ',姓名:', v_name) AS 用户信息;
    END LOOP;
    
    -- 4. 关闭游标(后文详细说明)
    CLOSE user_cursor;
END //
DELIMITER ; -- 恢复语句结束符为;

调用存储过程:

mysql> CALL process_user_cursor();
+--------------------------------+
| 用户信息                       |
+--------------------------------+
| 用户ID:1,姓名:张三          |
+--------------------------------+
1 row in set (0.00 sec)

+--------------------------------+
| 用户信息                       |
+--------------------------------+
| 用户ID:3,姓名:王五          |
+--------------------------------+
1 row in set (0.00 sec)

+--------------------------------+
| 用户信息                       |
+--------------------------------+
| 用户ID:5,姓名:孙七          |
+--------------------------------+
1 row in set (0.00 sec)

+-----------------------------------+
| 用户信息                          |
+-----------------------------------+
| 用户ID:9,姓名:钱十一           |
+-----------------------------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

通过游标修改 / 删除数据

部分游标(非 READ_ONLY)支持通过指针修改或删除当前行数据,以 MySQL 为例:

-- 如果存储过程已存在则删除
DROP PROCEDURE IF EXISTS process_user_cursor_handler;

-- 创建存储过程,新增修改/删除逻辑
DELIMITER // 
CREATE PROCEDURE process_user_cursor_handler()
BEGIN
    -- 声明变量存储游标数据
    DECLARE v_user_id INT;
    DECLARE v_name VARCHAR(50);
    DECLARE done INT DEFAULT FALSE; -- 标记游标是否遍历完成
    
    -- 1. 创建游标(查询男性用户,仅取user_id作为修改/删除的唯一标识)
    DECLARE user_cursor CURSOR FOR
        SELECT user_id, name FROM user WHERE gender = '男';
    
    -- 声明游标结束处理程序
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    
    -- 2. 打开游标
    OPEN user_cursor;
    
    -- 3. 循环读取游标数据并执行修改/删除
    read_loop: LOOP
        -- 读取下一行数据到变量
        FETCH user_cursor INTO v_user_id, v_name;
        
        -- 判断是否遍历完成,完成则退出循环
        IF done THEN
            LEAVE read_loop;
        END IF;
        
        -- 修改数据(根据user_id更新字段,可自定义条件和更新内容)
        -- 场景:将男性用户的address字段改为“默认地址”,phone字段加前缀“1-”
        UPDATE user 
        SET 
            address = '默认地址',  -- 自定义要修改的字段和值
            phone = CONCAT('1-', phone)  -- 基于原有值修改
        WHERE user_id = v_user_id; -- 必须用游标获取的v_user_id作为唯一条件,避免批量修改
        
        -- 打印操作结果(验证修改/删除是否触发)
        SELECT 
            CONCAT('用户ID:', v_user_id, ',姓名:', v_name, ' → 已执行修改操作') AS 操作结果;
    END LOOP;
    
    -- 4. 关闭游标
    CLOSE user_cursor;
    
    -- 可选:提交事务(如果关闭了自动提交)
    -- COMMIT;
END //
DELIMITER ;

调用存储过程:

mysql> CALL process_user_cursor_handler();
+----------------------------------------------------------+
| 操作结果                                                 |
+----------------------------------------------------------+
| 用户ID:1,姓名:张三 → 已执行修改操作                   |
+----------------------------------------------------------+
1 row in set (0.00 sec)

+----------------------------------------------------------+
| 操作结果                                                 |
+----------------------------------------------------------+
| 用户ID:3,姓名:王五 → 已执行修改操作                   |
+----------------------------------------------------------+
1 row in set (0.00 sec)

+----------------------------------------------------------+
| 操作结果                                                 |
+----------------------------------------------------------+
| 用户ID:5,姓名:孙七 → 已执行修改操作                   |
+----------------------------------------------------------+
1 row in set (0.00 sec)

+-------------------------------------------------------------+
| 操作结果                                                    |
+-------------------------------------------------------------+
| 用户ID:9,姓名:钱十一 → 已执行修改操作                    |
+-------------------------------------------------------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

 

关闭游标

游标使用完毕后,必须关闭以释放结果集占用的数据库资源,这是游标操作的收尾步骤,不可省略。

语法如下:

CLOSE 游标名称;

说明

  • 关闭游标后,结果集将被释放,但游标定义仍存在,可再次使用OPEN语句打开;

  • 若需彻底删除游标定义(释放所有关联资源),SQL Server 需额外执行DEALLOCATE 游标名称;,MySQL 则在存储过程结束后自动释放。

示例:

-- MySQL中关闭游标
CLOSE emp_cursor;

-- SQL Server中关闭并释放游标
CLOSE emp_cursor;
DEALLOCATE emp_cursor;

 

注意事项

(1) 游标效率较低,仅在逐行处理数据时使用,优先选择集合操作(如 UPDATE、SELECT 批量处理);

(2)必须遵循 “创建→打开→读取→关闭(→释放)” 的生命周期,避免资源泄漏;

(3)声明游标时建议指定READ_ONLY(只读),减少资源占用;

(4)遍历游标时需处理 “无数据” 场景(如 MySQL 的NOT FOUND处理器、SQL Server 的@@FETCH_STATUS),避免报错。

 

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号