游标(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 = '男';
创建游标后,需经过 “打开游标 → 读取数据 → 处理数据” 三步才能完成核心操作,这是游标的核心使用阶段。
打开游标会执行关联的 SELECT 语句,生成结果集并将游标指针定位到结果集的起始位置(第一行之前)。
语法如下:
OPEN 游标名称;
示例:
-- 打开上文创建的 user_cursor 游标 OPEN user_cursor;
读取数据是游标使用的核心,通过FETCH语句将游标指针移动到指定行,并将该行数据赋值给变量(需提前声明)。
基本语法:
FETCH [NEXT|PRIOR|FIRST|LAST] FROM 游标名称 INTO 变量1, 变量2, ...;
参数说明:
NEXT:读取下一行(最常用,默认值);
PRIOR:读取上一行(仅 SCROLL 游标支持);
FIRST/LAST:读取第一行 / 最后一行(仅 SCROLL 游标支持);
INTO:将读取的列数据赋值给对应的变量,变量数量、类型需与 SELECT 结果列一致。
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),避免报错。