下面是SQL 中游标(Cursor)的详细完整教程,适用于 MySQL、SQL Server、Oracle 和 PostgreSQL 的开发者,内容包含游标的定义、用法、性能考虑、错误处理、各数据库的语法区别等。


📘 SQL 游标(Cursor)详解教程


📌 目录

  1. 什么是游标?
  2. 使用游标的场景
  3. 游标的基本语法结构
  4. 示例:使用游标处理逐行记录
  5. SQL Server 游标示例
  6. MySQL 游标示例(存储过程内)
  7. Oracle 游标使用方式
  8. PostgreSQL 游标写法
  9. 游标的优缺点
  10. 替代方案与优化建议
  11. 常见错误与调试技巧
  12. 总结与参考资料

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' 的员工记录到日志中。

游标思路:

  1. 查询所有 inactive 员工;
  2. 对每一行记录插入日志表。

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)**或帮你重构现有游标逻辑,请贴出具体场景,我可以逐条帮你优化。