一、openGauss存储过程开发基础
存储过程是数据库开发中的瑞士军刀,它能将复杂的业务逻辑封装在数据库内部。在openGauss中,存储过程使用PL/pgSQL语法,这个和PostgreSQL非常相似,但又有一些华为特色的增强。
先来看个最简单的例子,我们创建一个计算员工奖金的存储过程:
-- 创建计算奖金的存储过程
CREATE OR REPLACE PROCEDURE calculate_bonus(
IN employee_id INT, -- 输入参数:员工ID
INOUT bonus_amount NUMERIC -- 输入输出参数:奖金金额
)
AS $$
DECLARE
base_salary NUMERIC; -- 声明局部变量:基本工资
performance_rating NUMERIC; -- 绩效评分
BEGIN
-- 获取员工基本工资
SELECT salary INTO base_salary
FROM employees
WHERE id = employee_id;
-- 获取员工绩效评分
SELECT rating INTO performance_rating
FROM performance_reviews
WHERE employee_id = employee_id;
-- 计算奖金(基本工资 × 绩效系数)
bonus_amount := base_salary * performance_rating * 0.1;
-- 记录奖金发放日志
INSERT INTO bonus_log(employee_id, amount, calc_date)
VALUES (employee_id, bonus_amount, CURRENT_DATE);
-- 异常处理
EXCEPTION WHEN NO_DATA_FOUND THEN
RAISE NOTICE '员工ID % 不存在或数据不完整', employee_id;
bonus_amount := 0;
END;
$$ LANGUAGE plpgsql;
这个例子展示了存储过程的基本结构:参数声明、变量定义、业务逻辑和异常处理。openGauss的存储过程支持事务控制,这意味着你可以在过程中使用COMMIT和ROLLBACK。
二、存储过程高级特性实战
openGauss提供了一些很酷的高级特性,能让你的存储过程更强大。比如动态SQL、游标处理和自定义异常。
2.1 动态SQL应用
动态SQL特别适合需要根据不同条件构建查询的场景:
-- 创建动态查询员工信息的存储过程
CREATE OR REPLACE PROCEDURE query_employees(
IN department_id INT DEFAULT NULL,
IN min_salary NUMERIC DEFAULT NULL,
OUT result_cursor REFCURSOR -- 返回游标用于客户端获取结果
)
AS $$
DECLARE
query_text TEXT; -- 动态SQL语句
BEGIN
-- 基础查询
query_text := 'SELECT id, name, salary FROM employees WHERE 1=1';
-- 根据参数动态添加条件
IF department_id IS NOT NULL THEN
query_text := query_text || ' AND department_id = ' || department_id;
END IF;
IF min_salary IS NOT NULL THEN
query_text := query_text || ' AND salary >= ' || min_salary;
END IF;
-- 打开游标返回结果
OPEN result_cursor FOR EXECUTE query_text;
-- 记录查询日志
INSERT INTO query_log(sql_text, exec_time)
VALUES (query_text, CURRENT_TIMESTAMP);
END;
$$ LANGUAGE plpgsql;
2.2 游标批量处理
处理大量数据时,游标能帮你节省内存:
-- 批量更新员工工资的存储过程
CREATE OR REPLACE PROCEDURE batch_update_salary(
IN increase_percent NUMERIC
)
AS $$
DECLARE
emp_rec RECORD; -- 定义记录类型变量
emp_cursor CURSOR FOR
SELECT id, salary FROM employees
WHERE status = 'active';
BEGIN
-- 开始事务
BEGIN
-- 打开游标
OPEN emp_cursor;
LOOP
-- 获取下一条记录
FETCH emp_cursor INTO emp_rec;
EXIT WHEN NOT FOUND;
-- 更新工资
UPDATE employees
SET salary = emp_rec.salary * (1 + increase_percent/100)
WHERE id = emp_rec.id;
-- 每处理100条记录提交一次
IF MOD(emp_rec.id, 100) = 0 THEN
COMMIT;
BEGIN; -- 开始新事务
END IF;
END LOOP;
-- 关闭游标
CLOSE emp_cursor;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE EXCEPTION '批量更新失败: %', SQLERRM;
END;
END;
$$ LANGUAGE plpgsql;
三、性能优化关键技巧
存储过程写出来容易,但要写得高效就需要一些技巧了。以下是几个openGauss特有的优化建议。
3.1 参数传递优化
openGauss对IN、OUT、INOUT参数的处理方式不同,会影响性能:
-- 不推荐的写法:频繁修改INOUT参数
CREATE OR REPLACE PROCEDURE slow_calculation(
INOUT counter INT
)
AS $$
BEGIN
FOR i IN 1..10000 LOOP
counter := counter + 1; -- 每次修改都会产生副本
END LOOP;
END;
$$ LANGUAGE plpgsql;
-- 推荐的优化写法
CREATE OR REPLACE PROCEDURE fast_calculation(
IN initial_value INT,
OUT result INT
)
AS $$
DECLARE
temp_counter INT := initial_value;
BEGIN
FOR i IN 1..10000 LOOP
temp_counter := temp_counter + 1; -- 操作局部变量
END LOOP;
result := temp_counter; -- 最后一次性赋值
END;
$$ LANGUAGE plpgsql;
3.2 批量操作替代循环
openGauss的批量DML操作比循环单条处理快得多:
-- 不推荐的写法:循环单条更新
CREATE OR REPLACE PROCEDURE slow_update_employees()
AS $$
DECLARE
emp RECORD;
BEGIN
FOR emp IN SELECT id FROM employees LOOP
UPDATE employees
SET last_updated = NOW()
WHERE id = emp.id;
END LOOP;
END;
$$ LANGUAGE plpgsql;
-- 推荐的优化写法:批量更新
CREATE OR REPLACE PROCEDURE fast_update_employees()
AS $$
BEGIN
UPDATE employees
SET last_updated = NOW()
WHERE status = 'active'; -- 一次性更新所有符合条件的记录
END;
$$ LANGUAGE plpgsql;
四、最佳实践与避坑指南
在实际项目中,我总结了一些血泪教训,分享给大家。
4.1 错误处理规范
完善的错误处理能让你的存储过程更健壮:
-- 良好的错误处理示例
CREATE OR REPLACE PROCEDURE transfer_funds(
IN from_account INT,
IN to_account INT,
IN amount NUMERIC,
OUT status TEXT
)
AS $$
DECLARE
current_balance NUMERIC;
BEGIN
-- 验证参数
IF amount <= 0 THEN
RAISE EXCEPTION '转账金额必须大于0';
END IF;
-- 检查账户余额
SELECT balance INTO current_balance
FROM accounts
WHERE account_id = from_account
FOR UPDATE; -- 加锁防止并发修改
IF current_balance < amount THEN
RAISE EXCEPTION '账户余额不足';
END IF;
-- 执行转账
BEGIN
-- 扣款
UPDATE accounts
SET balance = balance - amount
WHERE account_id = from_account;
-- 存款
UPDATE accounts
SET balance = balance + amount
WHERE account_id = to_account;
-- 记录交易
INSERT INTO transactions(from_account, to_account, amount, trans_time)
VALUES (from_account, to_account, amount, NOW());
COMMIT;
status := '转账成功';
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
status := '转账失败: ' || SQLERRM;
-- 记录详细错误日志
INSERT INTO error_logs(procedure_name, error_msg, error_time)
VALUES ('transfer_funds', SQLERRM, NOW());
END;
END;
$$ LANGUAGE plpgsql;
4.2 调试技巧
调试存储过程有时很头疼,试试这些方法:
-- 调试用的存储过程示例
CREATE OR REPLACE PROCEDURE complex_calculation(
IN input_param INT,
OUT result INT
)
AS $$
DECLARE
debug_flag BOOLEAN := TRUE; -- 调试开关
temp_val1 INT;
temp_val2 INT;
BEGIN
-- 第一步计算
SELECT some_value INTO temp_val1 FROM some_table WHERE id = input_param;
IF debug_flag THEN
RAISE NOTICE '第一步结果: %', temp_val1;
END IF;
-- 第二步计算
temp_val2 := temp_val1 * 2 + 100;
IF debug_flag THEN
RAISE NOTICE '第二步结果: %', temp_val2;
END IF;
-- 复杂逻辑
IF temp_val2 > 1000 THEN
result := temp_val2 / 10;
ELSE
result := temp_val2 * 2;
END IF;
IF debug_flag THEN
RAISE NOTICE '最终结果: %', result;
END IF;
END;
$$ LANGUAGE plpgsql;
4.3 维护建议
长期维护的存储过程需要注意这些:
- 为每个存储过程添加清晰的注释头
- 使用一致的命名规范(如p_前缀表示参数,v_前缀表示变量)
- 避免在存储过程中写死业务逻辑数值
- 定期审查和重构复杂的存储过程
- 为关键存储过程编写单元测试
-- 良好注释的存储过程示例
/*
* 功能: 计算员工年终奖金
* 作者: 张三
* 创建日期: 2023-01-15
* 修改历史:
* 2023-03-20 李四 - 增加绩效系数调整
* 2023-06-10 王五 - 修复除零错误
*/
CREATE OR REPLACE PROCEDURE calculate_annual_bonus(
IN p_emp_id INT, -- 员工ID
IN p_year INT, -- 年度
OUT p_bonus NUMERIC(10,2) -- 计算出的奖金
)
AS $$
DECLARE
v_base_salary NUMERIC(10,2); -- 基本工资
v_performance_factor NUMERIC(5,2) := 1.0; -- 绩效系数
BEGIN
-- 实现代码...
END;
$$ LANGUAGE plpgsql;
五、应用场景与技术选型
存储过程特别适合以下场景:
- 高频执行的复杂业务逻辑
- 需要保证数据完整性的多步操作
- 需要减少网络流量的批量操作
- 需要数据库层面封装的安全控制
openGauss存储过程的优势:
- 高性能:在数据库服务器端执行,减少网络开销
- 可维护性:业务逻辑集中管理
- 安全性:可以控制对基础表的直接访问
- 事务控制:复杂操作可以保证ACID特性
需要注意的局限性:
- 调试困难,不如应用代码方便
- 可能造成数据库服务器负载过高
- 版本控制和团队协作不如应用代码成熟
- 业务逻辑在数据库层可能导致应用层和数据库层耦合
评论