SQL Server游标 #
一、游标概述 #
1.1 什么是游标 #
游标是一种数据库对象,允许逐行处理结果集。虽然功能强大,但通常应该避免使用,因为性能较差。
text
游标特点:
├── 逐行处理数据
├── 可以定位到特定行
├── 支持数据修改
└── 性能开销大
1.2 游标生命周期 #
text
游标生命周期:
1. DECLARE CURSOR - 声明游标
2. OPEN CURSOR - 打开游标
3. FETCH - 获取数据
4. CLOSE CURSOR - 关闭游标
5. DEALLOCATE CURSOR - 释放游标
二、游标类型 #
2.1 游标类型对比 #
| 类型 | 说明 | 特点 |
|---|---|---|
| STATIC | 静态游标 | 数据快照,不反映变化 |
| DYNAMIC | 动态游标 | 实时反映变化 |
| FORWARD_ONLY | 只进游标 | 只能向前移动 |
| SCROLL | 滚动游标 | 可任意方向移动 |
| KEYSET | 键集游标 | 固定成员,值可变 |
2.2 游标范围 #
text
游标范围:
├── LOCAL - 局部游标(当前批处理)
└── GLOBAL - 全局游标(当前连接)
三、基本游标操作 #
3.1 声明游标 #
sql
-- 简单游标声明
DECLARE cur_users CURSOR FOR
SELECT id, name, email FROM users WHERE status = 1;
-- 完整游标声明
DECLARE cur_users CURSOR
LOCAL
STATIC
READ_ONLY
FOR
SELECT id, name, email FROM users WHERE status = 1;
3.2 打开游标 #
sql
-- 打开游标
OPEN cur_users;
-- 检查游标状态
IF @@CURSOR_ROWS > 0
PRINT 'Cursor opened with ' + CAST(@@CURSOR_ROWS AS VARCHAR) + ' rows';
ELSE
PRINT 'Cursor is empty or not opened';
3.3 获取数据 #
sql
-- 声明变量
DECLARE @id INT;
DECLARE @name NVARCHAR(50);
DECLARE @email VARCHAR(100);
-- 获取下一行
FETCH NEXT FROM cur_users INTO @id, @name, @email;
-- 检查获取状态
IF @@FETCH_STATUS = 0
PRINT 'ID: ' + CAST(@id AS VARCHAR) + ', Name: ' + @name;
ELSE IF @@FETCH_STATUS = -1
PRINT 'No more rows';
ELSE IF @@FETCH_STATUS = -2
PRINT 'Row does not exist';
-- @@FETCH_STATUS值:
-- 0:成功
-- -1:超出结果集
-- -2:行不存在
3.4 遍历游标 #
sql
-- 完整游标遍历
DECLARE @id INT;
DECLARE @name NVARCHAR(50);
DECLARE cur_users CURSOR FOR
SELECT id, name FROM users WHERE status = 1;
OPEN cur_users;
FETCH NEXT FROM cur_users INTO @id, @name;
WHILE @@FETCH_STATUS = 0
BEGIN
PRINT 'ID: ' + CAST(@id AS VARCHAR) + ', Name: ' + @name;
FETCH NEXT FROM cur_users INTO @id, @name;
END
CLOSE cur_users;
DEALLOCATE cur_users;
3.5 关闭和释放游标 #
sql
-- 关闭游标
CLOSE cur_users;
-- 释放游标
DEALLOCATE cur_users;
-- 检查游标是否存在
IF CURSOR_STATUS('global', 'cur_users') >= 0
PRINT 'Cursor exists';
四、游标选项 #
4.1 SCROLL游标 #
sql
-- 声明滚动游标
DECLARE cur_users SCROLL CURSOR FOR
SELECT id, name FROM users ORDER BY id;
OPEN cur_users;
-- 获取第一行
FETCH FIRST FROM cur_users;
-- 获取最后一行
FETCH LAST FROM cur_users;
-- 获取下一行
FETCH NEXT FROM cur_users;
-- 获取上一行
FETCH PRIOR FROM cur_users;
-- 获取绝对位置(第3行)
FETCH ABSOLUTE 3 FROM cur_users;
-- 获取相对位置(当前位置+2)
FETCH RELATIVE 2 FROM cur_users;
CLOSE cur_users;
DEALLOCATE cur_users;
4.2 可更新游标 #
sql
-- 声明可更新游标
DECLARE cur_users CURSOR FOR
SELECT id, name, salary FROM users WHERE status = 1
FOR UPDATE OF salary;
OPEN cur_users;
DECLARE @id INT;
DECLARE @name NVARCHAR(50);
DECLARE @salary DECIMAL(10,2);
FETCH NEXT FROM cur_users INTO @id, @name, @salary;
WHILE @@FETCH_STATUS = 0
BEGIN
-- 更新当前行
UPDATE users
SET salary = @salary * 1.1
WHERE CURRENT OF cur_users;
FETCH NEXT FROM cur_users INTO @id, @name, @salary;
END
CLOSE cur_users;
DEALLOCATE cur_users;
4.3 游标类型示例 #
sql
-- STATIC游标(快照)
DECLARE cur_static CURSOR
STATIC
FOR
SELECT id, name FROM users;
-- DYNAMIC游标(实时)
DECLARE cur_dynamic CURSOR
DYNAMIC
FOR
SELECT id, name FROM users;
-- KEYSET游标
DECLARE cur_keyset CURSOR
KEYSET
FOR
SELECT id, name FROM users;
-- FORWARD_ONLY游标
DECLARE cur_forward CURSOR
FORWARD_ONLY
FOR
SELECT id, name FROM users;
五、游标函数 #
5.1 @@CURSOR_ROWS #
sql
-- 获取游标行数
OPEN cur_users;
SELECT @@CURSOR_ROWS AS cursor_rows;
-- 返回值:
-- -m:异步填充,已获取m行
-- -1:动态游标,行数不确定
-- 0:未打开或无行
-- n:已填充n行
5.2 @@FETCH_STATUS #
sql
-- 获取获取状态
FETCH NEXT FROM cur_users;
SELECT @@FETCH_STATUS AS fetch_status;
-- 返回值:
-- 0:成功
-- -1:超出结果集
-- -2:行不存在
5.3 CURSOR_STATUS #
sql
-- 检查游标状态
SELECT CURSOR_STATUS('global', 'cur_users') AS status;
-- 返回值:
-- 1:已打开,有行
-- 0:已打开,无行
-- -1:已关闭
-- -2:不适用
-- -3:不存在
六、游标存储过程 #
6.1 sp_cursor_list #
sql
-- 查看所有游标
EXEC sp_cursor_list @cursor_return = 0, @cursor_scope = 1;
-- @cursor_scope: 1=全局, 2=局部, 3=全部
6.2 sp_describe_cursor #
sql
-- 查看游标详细信息
DECLARE @cursor_report CURSOR;
EXEC sp_describe_cursor @cursor_return = @cursor_report OUTPUT, @cursor_source = 'global', @cursor_identity = 'cur_users';
七、替代方案 #
7.1 使用集合操作 #
sql
-- 使用游标(不推荐)
DECLARE cur_users CURSOR FOR SELECT id FROM users;
OPEN cur_users;
DECLARE @id INT;
FETCH NEXT FROM cur_users INTO @id;
WHILE @@FETCH_STATUS = 0
BEGIN
UPDATE users SET status = 1 WHERE id = @id;
FETCH NEXT FROM cur_users INTO @id;
END
CLOSE cur_users;
DEALLOCATE cur_users;
-- 使用集合操作(推荐)
UPDATE users SET status = 1;
7.2 使用WHILE循环 #
sql
-- 使用WHILE替代游标
DECLARE @id INT;
DECLARE @name NVARCHAR(50);
-- 获取第一行
SELECT TOP 1 @id = id, @name = name
FROM users
WHERE status = 1
ORDER BY id;
WHILE @id IS NOT NULL
BEGIN
PRINT 'ID: ' + CAST(@id AS VARCHAR) + ', Name: ' + @name;
-- 获取下一行
SELECT TOP 1 @id = id, @name = name
FROM users
WHERE status = 1 AND id > @id
ORDER BY id;
IF @@ROWCOUNT = 0
SET @id = NULL;
END
7.3 使用CTE #
sql
-- 使用CTE处理
WITH numbered_users AS (
SELECT
id,
name,
ROW_NUMBER() OVER (ORDER BY id) AS row_num
FROM users
WHERE status = 1
)
SELECT * FROM numbered_users;
八、游标最佳实践 #
8.1 何时使用游标 #
text
适合使用游标的场景:
├── 复杂的逐行处理逻辑
├── 需要调用存储过程处理每行
├── 无法用集合操作实现
└── 需要特定的行定位操作
避免使用游标的场景:
├── 简单的数据更新
├── 可以用集合操作替代
├── 大数据量处理
└── 性能敏感的操作
8.2 性能优化 #
sql
-- 使用FAST_FORWARD游标(性能最优)
DECLARE cur_users CURSOR
FAST_FORWARD
FOR
SELECT id, name FROM users;
-- 使用LOCAL游标
DECLARE cur_users CURSOR
LOCAL FAST_FORWARD
FOR
SELECT id, name FROM users;
-- 尽早关闭和释放游标
OPEN cur_users;
-- 操作
CLOSE cur_users;
DEALLOCATE cur_users;
8.3 游标模板 #
sql
-- 推荐的游标模板
DECLARE @id INT;
DECLARE @name NVARCHAR(50);
DECLARE cur_users CURSOR
LOCAL FAST_FORWARD
FOR
SELECT id, name FROM users WHERE status = 1;
OPEN cur_users;
FETCH NEXT FROM cur_users INTO @id, @name;
WHILE @@FETCH_STATUS = 0
BEGIN
-- 处理逻辑
PRINT 'Processing: ' + @name;
FETCH NEXT FROM cur_users INTO @id, @name;
END
CLOSE cur_users;
DEALLOCATE cur_users;
九、总结 #
游标要点:
| 方面 | 说明 |
|---|---|
| 类型 | STATIC、DYNAMIC、KEYSET |
| 选项 | SCROLL、FORWARD_ONLY |
| 操作 | DECLARE、OPEN、FETCH、CLOSE |
| 状态 | @@FETCH_STATUS、@@CURSOR_ROWS |
最佳实践:
- 优先使用集合操作
- 使用FAST_FORWARD游标
- 尽早关闭和释放
- 避免在大数据量时使用
- 考虑使用WHILE替代
下一步,让我们学习触发器!
最后更新:2026-03-27