下面是SQL 中游标(Cursor)的详细完整教程,适用于 MySQL、SQL Server、Oracle 和 PostgreSQL 的开发者,内容包含游标的定义、用法、性能考虑、错误处理、各数据库的语法区别等。
📘 SQL 游标(Cursor)详解教程
📌 目录
- 什么是游标?
- 使用游标的场景
- 游标的基本语法结构
- 示例:使用游标处理逐行记录
- SQL Server 游标示例
- MySQL 游标示例(存储过程内)
- Oracle 游标使用方式
- PostgreSQL 游标写法
- 游标的优缺点
- 替代方案与优化建议
- 常见错误与调试技巧
- 总结与参考资料
1. 🧠 什么是游标(Cursor)?
游标(Cursor)是一种数据库对象,用于逐行遍历查询结果集。
通常用于处理复杂的逐条逻辑(如逐行更新、嵌套判断等)。
2. 🧾 使用游标的场景
场景 | 是否适合使用游标 |
---|---|
每行需要不同的业务逻辑处理 | ✅ |
条件判断、复杂 IF/CASE 逻辑 | ✅ |
需要调用存储过程或触发器 | ✅ |
可用 SQL 批量语句代替的场景 | ❌(尽量避免) |
要求高性能、大数据处理 | ❌ |
3. 🏗️ 游标的基本语法结构(通用)
-- 声明变量(存储字段值)
DECLARE @name VARCHAR(100)
-- 声明游标
DECLARE my_cursor CURSOR FOR
SELECT name FROM employees WHERE status = 'active';
-- 打开游标
OPEN my_cursor;
-- 获取第一行
FETCH NEXT FROM my_cursor INTO @name;
-- 开始循环
WHILE @@FETCH_STATUS = 0
BEGIN
-- 对每一行进行处理
PRINT '处理员工:' + @name;
-- 获取下一行
FETCH NEXT FROM my_cursor INTO @name;
END;
-- 关闭并释放游标
CLOSE my_cursor;
DEALLOCATE my_cursor;
4. 📄 示例:使用游标处理员工表
假设有如下员工表:
CREATE TABLE employees (
id INT,
name VARCHAR(100),
status VARCHAR(20)
);
我们要把所有 status = 'inactive'
的员工记录到日志中。
游标思路:
- 查询所有 inactive 员工;
- 对每一行记录插入日志表。
5. 💼 SQL Server 游标完整示例
-- 示例:遍历所有 inactive 员工并写入日志表
DECLARE @emp_id INT, @emp_name VARCHAR(100)
DECLARE emp_cursor CURSOR FOR
SELECT id, name FROM employees WHERE status = 'inactive'
OPEN emp_cursor
FETCH NEXT FROM emp_cursor INTO @emp_id, @emp_name
WHILE @@FETCH_STATUS = 0
BEGIN
INSERT INTO employee_log(emp_id, emp_name, log_time)
VALUES (@emp_id, @emp_name, GETDATE())
FETCH NEXT FROM emp_cursor INTO @emp_id, @emp_name
END
CLOSE emp_cursor
DEALLOCATE emp_cursor
6. 🐬 MySQL 游标示例(必须在存储过程中)
DELIMITER $$
CREATE PROCEDURE process_employees()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE emp_id INT;
DECLARE emp_name VARCHAR(100);
DECLARE emp_cursor CURSOR FOR
SELECT id, name FROM employees WHERE status = 'inactive';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN emp_cursor;
read_loop: LOOP
FETCH emp_cursor INTO emp_id, emp_name;
IF done THEN
LEAVE read_loop;
END IF;
-- 示例操作:插入日志
INSERT INTO employee_log(emp_id, emp_name, log_time)
VALUES (emp_id, emp_name, NOW());
END LOOP;
CLOSE emp_cursor;
END$$
DELIMITER ;
7. 🧱 Oracle 游标示例
显式游标(PL/SQL):
DECLARE
CURSOR emp_cursor IS
SELECT id, name FROM employees WHERE status = 'inactive';
emp_record emp_cursor%ROWTYPE;
BEGIN
OPEN emp_cursor;
LOOP
FETCH emp_cursor INTO emp_record;
EXIT WHEN emp_cursor%NOTFOUND;
INSERT INTO employee_log(emp_id, emp_name, log_time)
VALUES (emp_record.id, emp_record.name, SYSDATE);
END LOOP;
CLOSE emp_cursor;
END;
8. 🐘 PostgreSQL 游标写法(示例)
PostgreSQL 游标通常结合函数或事务使用:
BEGIN;
DECLARE emp_cursor CURSOR FOR
SELECT id, name FROM employees WHERE status = 'inactive';
FETCH NEXT FROM emp_cursor;
-- 可重复 FETCH 下一行处理,适合复杂事务脚本
CLOSE emp_cursor;
COMMIT;
或在函数中使用 LOOP
结合 FETCH
处理多行逻辑。
9. ⚖️ 游标的优缺点
✅ 优点:
- 可逐行处理复杂逻辑;
- 可配合存储过程、条件语句处理动态内容;
- 适用于无法批处理的逻辑处理。
❌ 缺点:
- 性能差:逐行执行,效率远低于批处理;
- 维护复杂,出错概率高;
- 并发性较差(尤其在大数据量环境)。
10. 🚀 替代方案与优化建议
操作目标 | 替代写法 |
---|---|
条件更新 | UPDATE ... WHERE ... |
汇总、计算 | GROUP BY 、窗口函数 |
循环判断逻辑复杂 | 使用临时表 + 批处理 |
遍历数据处理 | 写成客户端程序读取结果后处理(如 Python/PHP) |
11. 🐞 常见错误与调试技巧
错误情况 | 解决建议 |
---|---|
@@FETCH_STATUS 始终为 -1 | 没有 OPEN 游标或查询无结果 |
游标未释放导致内存泄漏 | 确保 CLOSE 和 DEALLOCATE |
MySQL 提示不能在 SQL 中使用游标 | 游标只能在存储过程中定义和使用 |
Oracle %NOTFOUND 无效 | 确保先 FETCH 才判断 |
12. 📚 总结与参考
游标是 SQL 中实现“逐行处理”的一种方式,尽管不推荐在高性能场景频繁使用,但在必须使用时应注意:
- 必须明确
OPEN → FETCH → CLOSE
的生命周期; - 优先考虑批处理替代方案;
- 在存储过程或复杂业务逻辑中适度使用。
🔗 参考资料(出站链接)
如需我为你生成某种数据库的**游标模板代码文件(.sql/.plsql/.psql)**或帮你重构现有游标逻辑,请贴出具体场景,我可以逐条帮你优化。
发表回复