在数据库编程中,游标 (Cursor) 是一种用于逐行处理查询结果的机制,尤其在涉及多行数据操作的场景(如存储过程或触发器)中非常有用。以下是其核心概念和基本作用:

游标的基本作用

  1. 逐行访问
    传统 SQL 查询(如 SELECT)会一次性返回所有结果,而游标允许程序逐行读取结果集,类似于迭代器或指针。
  2. 处理多行结果
    当结果集包含多行数据时(例如更新一系列记录的状态),游标可以配合同 WHILEREPEAT 等循环结构,对每一行执行特定操作。
  3. 在存储过程中实现复杂逻辑
    游标常用于存储过程或函数中,结合条件判断和 SQL 语句,处理动态数据。

游标的四个关键步骤

  1. 声明游标
    定义游标名称和与之关联的 SQL 查询。
    示例(MySQL 语法):

    DECLARE cursor_name Cursor FOR SELECT ... FROM ...;
  2. 打开游标
    立即执行游标的查询语句,将结果集加载到内存中。
    语法:

    OPEN cursor_name;
  3. 提取数据
    使用游标逐行获取数据,通常通过 FETCH 语句将其存入变量中。
    语法:

    FETCH FROM cursor_name INTO variable1, variable2, ...;
  4. 关闭游标
    释放游标占用的资源和结果集。
    语法:

    CLOSE cursor_name;

典型使用场景

  1. 批量更新/删除
    例如,银行系统中逐行查询每个客户的交易记录,并根据条件调整其账户余额:

    DECLARE c CURSOR FOR SELECT account_id, amount FROM transactions;
    OPEN c;
    WHILE 1=1 DO
        FETCH c INTO acct_id, trans_amount;
        IF SQLSTATE = '02000' THEN LEAVE; END IF;
        UPDATE accounts SET balance = balance - trans_amount WHERE id = acct_id;
    END WHILE;
    CLOSE c;
  2. 验证行数据
    在电子商务中,检查订单的付款状态,若未支付则发送通知:

    DECLARE c CURSOR FOR SELECT order_id, customer_email FROM orders WHERE status = 'Pending';
    FETCH c INTO order_id, email;
    CALL send_notification(order_id, email);  -- 调用另一存储过程
  3. 复杂条件处理
    学生管理系统中统计超过平均成绩的学生成绩:

    DECLARE avg_view CURSOR FOR SELECT name, score FROM students;
    DECLARE avg INT;
    DECLARE total INT DEFAULT 0;
    DECLARE count INT DEFAULT 0;
    OPEN avg_view;
    cycle_view: LOOP
        FETCH avg_view INTO stu_name, stu_score;
        IF SQLSTATE = '02000' THEN LEAVE cycle_view; END IF;
        SET total = total + stu_score;
        SET count = count + 1;
    END LOOP cycle_view;
    SET avg = total / count;
    SELECT name, score FROM students WHERE score > avg;  -- 展示高于平均的成绩
  4. 复合业务逻辑
    在存储过程中计算每个用户的订单总额并更新其信用状态:

    DECLARE user_cursor CURSOR FOR SELECT user_id FROM users;
    OPEN user_cursor;
    user_loop: LOOP
        FETCH user_cursor INTO user_id;
        IF SQLSTATE = '02000' THEN LEAVE user_loop; END IF;
        START TRANSACTION;
            SELECT SUM(price) INTO user_total FROM orders WHERE user_id = user_id;
            IF user_total > 10000 THEN
                UPDATE users SET credit_status = 'Gold' WHERE id = user_id;
            END IF;
        COMMIT;
    END LOOP user_loop;
    CLOSE user_cursor;
  5. 数据输出/格式化
    在日志或报表系统中,逐行生成格式化后的结果:

    DECLARE report_cursor CURSOR FOR SELECT id, name, salary FROM employees;
    OPEN report_cursor;
    cycle_report: LOOP
        FETCH report_cursor INTO emp_id, emp_name, emp_salary;
        IF SQLSTATE = '02000' THEN LEAVE cycle_report; END IF;
        SELECT CONCAT(emp_name, ' earns $', emp_salary) AS message;
    END LOOP;
    CLOSE report_cursor;

需要注意的问题

  1. 性能开销
    游标需要逐行处理,会带来性能延迟(尤其在大数据量时),通常应避免在应用层使用。
  2. 事务控制
    游标中的操作(如 UPDATEDELETE)应配合同事务(START TRANSACTIONCOMMIT/ROLLBACK),以确保数据一致性。
  3. 错误处理
    在 Fortran 或 MySQL 手册中,当游标到达结果集末尾时(MySQL 中 SQLSTATE = '02000'),需配合 LEAVEBREAK 退出循环。
  4. 游标限制
    有些数据库(如 PostgreSQL)不支持在函数中打开/关闭游标,需根据环境调整逻辑。

示例分析

  • 场景:从产品库存表中找出库存量低于安全阈值的产品并补充库存。
  • 代码逻辑:
    CREATE PROCEDURE check_stock(IN threshold INT)
    BEGIN
        DECLARE done INT DEFAULT 0;
        DECLARE product_id INT;
        DECLARE current_stock INT;
        DECLARE cur_stock CURSOR FOR SELECT id, stock FROM products WHERE stock < threshold;
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
     
        OPEN cur_stock;
     
        cycle_product: LOOP
            FETCH cur_stock INTO product_id, current_stock;
            IF done = 1 THEN LEAVE cycle_product; END IF;
            UPDATE products SET stock = stock + 100 WHERE id = product_id;
        END LOOP cycle_product;
     
        CLOSE cur_stock;
    END;
    • 流程解释:
      1. 声明游标 cur_stock,查询当前库存低于阈值的产品。
      2. 声明 CONTINUE HANDLER 用于检测结果集是否结束(NOT FOUND 状态)。
      3. 打开游标后,通过循环逐行提取 idstock,并对每个产品执行 UPDATE 操作。
      4. 最后关闭游标,释放资源。
    • 结果:所有库存不足的产品都会自动补货 100 个单位。

结论

游标是 SQL 存储过程中处理多行结果复杂逻辑的核心工具,尤其适合对每一行进行差异化操作(如条件判断、循环处理)。但需注意其性能代价,建议在必要时(如无法用集合操作实现时)才使用,并尽量优化结果集大小。