GaussDB数据库SQL系列:游标管理深度解析与实战指南
一、游标的核心价值
1.1 数据逐行处理能力
精细控制:逐行遍历结果集(如订单明细处理)
复杂业务逻辑:实现行级条件判断(如数据清洗规则)
状态保持:在事务中维持中间处理状态(如批次处理计数)
1.2 典型应用场景
场景 实现方式 性能特征
数据迁移 逐行转换后插入目标表 适合中小数据量
批次处理 每1000行提交事务 平衡内存与事务开销
动态计算 实时累加统计值 低延迟逐行处理
二、游标类型与适用场景
- 静态游标 vs 动态游标
-- 静态游标(绑定查询)
DECLARE cur_static CURSOR FOR
SELECT * FROM orders WHERE status = 'pending';
-- 动态游标(参数化查询)
DECLARE cur_dynamic CURSOR (p_status VARCHAR)
FOR SELECT * FROM orders WHERE status = p_status;
- 显式游标 vs 隐式游标
-- 显式游标管理
OPEN cur_orders;
LOOP
FETCH cur_orders INTO order_rec;
EXIT WHEN cur_orders%NOTFOUND;
-- 处理逻辑...
END LOOP;
CLOSE cur_orders;
-- 隐式游标(FOR循环)
FOR order_rec IN (SELECT * FROM orders) LOOP
-- 自动游标管理
END LOOP;
- 服务器端游标 vs 客户端游标
特性 服务器端游标 客户端游标
内存占用 数据库服务器内存 客户端内存
网络传输 分批次拉取 一次性传输结果集
适用场景 大数据量处理(>10万行) 中小数据量(<1万行)
三、游标管理全流程
- 基础操作模板
-- 声明阶段
DECLARE
cur_orders CURSOR FOR
SELECT order_id, amount FROM orders
WHERE create_time > '2023-01-01';
v_order_id BIGINT;
v_amount NUMERIC(10,2);
BEGIN
-- 打开游标
OPEN cur_orders;
-- 循环获取
LOOP
FETCH cur_orders INTO v_order_id, v_amount;
EXIT WHEN NOT FOUND;
-- 业务处理(示例:金额转换)
IF v_amount > 1000 THEN
INSERT INTO large_orders VALUES (v_order_id, v_amount);
END IF;
END LOOP;
-- 关闭游标
CLOSE cur_orders;
END;
- 异常处理机制
DECLARE
cur_data CURSOR FOR
SELECT * FROM sensitive_data;
v_row RECORD;
BEGIN
OPEN cur_data;
BEGIN
LOOP
FETCH cur_data INTO v_row;
EXIT WHEN NOT FOUND;
-- 可能出错的敏感操作
PERFORM process_sensitive_data(v_row);
END LOOP;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE NOTICE 'Error at row: %', v_row.id;
END;
CLOSE cur_data;
END;
四、高级应用技巧
- 游标变量传递
-- 创建参数化游标函数
CREATE OR REPLACE FUNCTION get_orders_by_status(
p_status VARCHAR
) RETURNS SETOF RECORD AS $$
DECLARE
cur refcursor;
BEGIN
OPEN cur FOR
SELECT * FROM orders WHERE status = p_status;
RETURN NEXT cur;
END;
$$ LANGUAGE plpgsql;
-- 调用示例
SELECT * FROM get_orders_by_status('shipped');
- 批量获取优化
-- 使用ARRAY抓取(每次获取1000行)
DECLARE
cur_ref refcursor;
rows_arr RECORD[];
BEGIN
OPEN cur_ref FOR SELECT * FROM large_table;
LOOP
FETCH cur_ref INTO rows_arr;
EXIT WHEN rows_arr IS NULL;
-- 批量处理(示例:更新状态)
UPDATE target_table
SET processed = true
WHERE id = ANY(rows_arr.id_arr);
END LOOP;
CLOSE cur_ref;
END;
五、性能优化策略
- 游标参数调优
参数 推荐值 效果
cursor_tuple_fraction 0.1~0.3 减少预取数据量
work_mem 4MB~64MB 提升排序/哈希性能
max_parallel_workers 2~8 并行处理游标数据 - 分页游标实现
-- 基于OFFSET的分页(适合中小数据)
DECLARE
cur_page CURSOR (p_offset INT, p_limit INT)
FOR SELECT * FROM products
ORDER BY id
OFFSET p_offset LIMIT p_limit;
-- 基于ROW_NUMBER的分页(大数据推荐)
CREATE OR REPLACE FUNCTION get_paginated_data(
p_page INT DEFAULT 1,
p_size INT DEFAULT 100
) RETURNS TABLE(id BIGINT, name VARCHAR) AS $$
BEGIN
RETURN QUERY
SELECT t.id, t.name
FROM (
SELECT *, ROW_NUMBER() OVER (ORDER BY create_time) AS rn
FROM products
) t
WHERE t.rn BETWEEN (p_page-1)*p_size+1 AND p_page*p_size;
END;
$$ LANGUAGE plpgsql;
六、避坑指南
- 游标泄漏检测
-- 查看未关闭游标
SELECT pid, usename, query
FROM pg_stat_activity
WHERE state = 'idle in transaction';
-- 自动关闭机制
CREATE OR REPLACE FUNCTION safe_cursor(
p_query TEXT
) RETURNS VOID AS $$
DECLARE
cur refcursor;
BEGIN
OPEN cur FOR EXECUTE p_query;
BEGIN
-- 业务逻辑...
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'Error occurred';
END;
CLOSE cur; -- 确保关闭
END;
$$ LANGUAGE plpgsql;
- 大数据量陷阱
-- 错误示例:一次性获取百万行
DECLARE
cur_big CURSOR FOR SELECT * FROM huge_table;
...
-- 正确做法:分批次处理
CREATE OR REPLACE FUNCTION process_big_data()
RETURNS VOID AS $$
DECLARE
batch_size INT := 5000;
total_rows INT;
BEGIN
LOOP
WITH chunk AS (
SELECT * FROM huge_table
LIMIT batch_size OFFSET (total_rows := total_rows + batch_size)
)
INSERT INTO processed_data
SELECT * FROM chunk;
EXIT WHEN NOT FOUND;
END LOOP;
END;
$$ LANGUAGE plpgsql;