1. 首页 > PostgreSQL教程 > 正文

PostgreSQL教程FG163-PL/pgSQL进阶:游标/异常处理/事务

本文档风哥主要介绍PostgreSQL数据库PL/pgSQL的进阶内容,包括游标、异常处理和事务的使用,风哥教程参考PostgreSQL官方文档PL/pgSQL内容,适合数据库开发人员和DBA在生产环境中处理复杂的业务逻辑。

Part01-基础概念与理论知识

1.1 PostgreSQL数据库游标概念

游标是一种数据库对象,用于处理查询结果集。在PL/pgSQL中,游标可以用于遍历结果集、处理大量数据等场景。更多视频教程www.fgedu.net.cn。

PostgreSQL数据库游标特点:

  • 可以逐行处理查询结果
  • 支持定位和滚动
  • 可以处理大量数据
  • 支持参数化查询
  • 可以在函数和存储过程中使用

1.2 PostgreSQL数据库异常处理概念

异常处理是PL/pgSQL中的重要特性,用于捕获和处理运行时错误,提高代码的健壮性。学习交流加群风哥微信: itpux-com。

PostgreSQL数据库异常处理特点:

  • 可以捕获和处理各种错误
  • 支持自定义异常
  • 可以获取错误信息
  • 支持嵌套异常处理
  • 可以在事务中使用

1.3 PostgreSQL数据库事务概念

事务是一组原子操作,要么全部成功,要么全部失败。在PL/pgSQL中,事务可以确保数据的一致性和完整性。

PostgreSQL数据库事务特点:

  • 原子性:事务要么全部成功,要么全部失败
  • 一致性:事务执行前后数据保持一致
  • 隔离性:事务之间相互隔离
  • 持久性:事务一旦提交,数据永久保存
  • 支持嵌套事务

Part02-生产环境规划与建议

2.1 PostgreSQL数据库游标设计原则

游标设计原则:只在必要时使用游标,避免频繁使用;合理设置游标参数,提高性能;及时关闭游标,释放资源;处理大量数据时使用游标;监控游标执行情况。

2.2 PostgreSQL数据库异常处理设计原则

异常处理设计原则:捕获特定异常,避免捕获所有异常;提供详细的错误信息;在适当的地方处理异常;避免过度使用异常;记录异常信息,便于排查问题。

2.3 PostgreSQL数据库事务设计原则

事务设计原则:保持事务简短,减少锁定时间;合理设置事务隔离级别;避免长事务;使用批量操作,减少事务次数;监控事务执行情况。

风哥提示:在设计和开发PL/pgSQL代码时,需要合理使用游标、异常处理和事务,以提高代码的性能和可靠性。建议:1) 只在必要时使用游标;2) 完善异常处理机制;3) 合理设计事务逻辑。

Part03-生产环境项目实施方案

3.1 PostgreSQL数据库游标实现

3.1.1 游标实现

— 游标实现

— 创建测试表
CREATE TABLE fgedu_employees (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
department VARCHAR(100) NOT NULL,
salary NUMERIC(10,2) NOT NULL,
hire_date DATE DEFAULT CURRENT_DATE
);

— 输出结果
CREATE TABLE

— 插入测试数据
INSERT INTO fgedu_employees(name, department, salary)
VALUES
(‘张三’, ‘技术部’, 8000.00),
(‘李四’, ‘销售部’, 6000.00),
(‘王五’, ‘技术部’, 9000.00),
(‘赵六’, ‘财务部’, 7000.00),
(‘孙七’, ‘销售部’, 6500.00),
(‘周八’, ‘技术部’, 9500.00);

— 输出结果
INSERT 0 6

— 基本游标使用
CREATE OR REPLACE FUNCTION fgedu_cursor_basic()
RETURNS TEXT
AS $$
DECLARE
cur_employees REFCURSOR;
v_employee fgedu_employees%ROWTYPE;
v_result TEXT := ”;
BEGIN
— 打开游标
OPEN cur_employees FOR
SELECT * FROM fgedu_employees ORDER BY department, salary DESC;

— 遍历游标
LOOP
FETCH cur_employees INTO v_employee;
EXIT WHEN NOT FOUND;
v_result := v_result || v_employee.department || ‘: ‘ || v_employee.name || ‘ (‘ || v_employee.salary || ‘), ‘;
END LOOP;

— 关闭游标
CLOSE cur_employees;

— 处理结果
v_result := rtrim(v_result, ‘, ‘);
RETURN v_result;
END;
$$ LANGUAGE plpgsql;

— 输出结果
CREATE FUNCTION

— 测试基本游标
SELECT fgedu_cursor_basic();

— 输出结果
fgedu_cursor_basic
——————————————————
财务部: 赵六 (7000.00), 技术部: 周八 (9500.00), 技术部: 王五 (9000.00), 技术部: 张三 (8000.00), 销售部: 孙七 (6500.00), 销售部: 李四 (6000.00)
(1 row)

— 带参数的游标
CREATE OR REPLACE FUNCTION fgedu_cursor_with_params(IN p_department VARCHAR(100))
RETURNS TEXT
AS $$
DECLARE
cur_employees REFCURSOR;
v_employee fgedu_employees%ROWTYPE;
v_result TEXT := ”;
BEGIN
— 打开带参数的游标
OPEN cur_employees FOR
SELECT * FROM fgedu_employees WHERE department = p_department ORDER BY salary DESC;

— 遍历游标
LOOP
FETCH cur_employees INTO v_employee;
EXIT WHEN NOT FOUND;
v_result := v_result || v_employee.name || ‘ (‘ || v_employee.salary || ‘), ‘;
END LOOP;

— 关闭游标
CLOSE cur_employees;

— 处理结果
IF v_result = ” THEN
RETURN ‘No employees found in ‘ || p_department;
ELSE
v_result := rtrim(v_result, ‘, ‘);
RETURN ‘Employees in ‘ || p_department || ‘: ‘ || v_result;
END IF;
END;
$$ LANGUAGE plpgsql;

— 输出结果
CREATE FUNCTION

— 测试带参数的游标
SELECT fgedu_cursor_with_params(‘技术部’);

— 输出结果
fgedu_cursor_with_params
—————————————
Employees in 技术部: 周八 (9500.00), 王五 (9000.00), 张三 (8000.00)
(1 row)

— 游标变量
CREATE OR REPLACE FUNCTION fgedu_cursor_variable()
RETURNS TEXT
AS $$
DECLARE
v_cursor REFCURSOR;
v_employee fgedu_employees%ROWTYPE;
v_result TEXT := ”;
BEGIN
— 动态打开游标
OPEN v_cursor FOR
SELECT * FROM fgedu_employees WHERE salary > 7000 ORDER BY salary DESC;

— 遍历游标
LOOP
FETCH v_cursor INTO v_employee;
EXIT WHEN NOT FOUND;
v_result := v_result || v_employee.name || ‘ (‘ || v_employee.salary || ‘), ‘;
END LOOP;

— 关闭游标
CLOSE v_cursor;

— 处理结果
v_result := rtrim(v_result, ‘, ‘);
RETURN ‘High salary employees: ‘ || v_result;
END;
$$ LANGUAGE plpgsql;

— 输出结果
CREATE FUNCTION

— 测试游标变量
SELECT fgedu_cursor_variable();

— 输出结果
fgedu_cursor_variable
————————————————–
High salary employees: 周八 (9500.00), 王五 (9000.00), 张三 (8000.00)
(1 row)

3.2 PostgreSQL数据库异常处理实现

3.2.1 异常处理实现

— 异常处理实现

— 基本异常处理
CREATE OR REPLACE FUNCTION fgedu_exception_basic(IN p_id INTEGER)
RETURNS TEXT
AS $$
DECLARE
v_employee fgedu_employees%ROWTYPE;
BEGIN
BEGIN
SELECT * INTO v_employee FROM fgedu_employees WHERE id = p_id;
RETURN ‘Employee found: ‘ || v_employee.name;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN ‘Employee not found’;
WHEN OTHERS THEN
RETURN ‘Error: ‘ || SQLERRM;
END;
END;
$$ LANGUAGE plpgsql;

— 输出结果
CREATE FUNCTION

— 测试基本异常处理
SELECT fgedu_exception_basic(1);

— 输出结果
fgedu_exception_basic
———————–
Employee found: 张三
(1 row)

SELECT fgedu_exception_basic(999);

— 输出结果
fgedu_exception_basic
———————–
Employee not found
(1 row)

— 自定义异常
CREATE OR REPLACE FUNCTION fgedu_exception_custom(IN p_salary NUMERIC(10,2))
RETURNS TEXT
AS $$
BEGIN
IF p_salary < 0 THEN RAISE EXCEPTION 'Salary cannot be negative'; ELSIF p_salary > 100000 THEN
RAISE EXCEPTION ‘Salary too high’;
END IF;

RETURN ‘Valid salary: ‘ || p_salary;
END;
$$ LANGUAGE plpgsql;

— 输出结果
CREATE FUNCTION

— 测试自定义异常
SELECT fgedu_exception_custom(5000.00);

— 输出结果
fgedu_exception_custom
————————
Valid salary: 5000.00
(1 row)

SELECT fgedu_exception_custom(-1000.00);

— 输出结果
ERROR: Salary cannot be negative

— 嵌套异常处理
CREATE OR REPLACE FUNCTION fgedu_exception_nested(IN p_id INTEGER, IN p_new_salary NUMERIC(10,2))
RETURNS TEXT
AS $$
DECLARE
v_employee fgedu_employees%ROWTYPE;
BEGIN
BEGIN
— 查找员工
SELECT * INTO v_employee FROM fgedu_employees WHERE id = p_id;

— 检查薪资
BEGIN
IF p_new_salary < 0 THEN RAISE EXCEPTION 'Salary cannot be negative'; END IF; -- 更新薪资 UPDATE fgedu_employees SET salary = p_new_salary WHERE id = p_id; RETURN 'Salary updated for ' || v_employee.name; EXCEPTION WHEN OTHERS THEN RETURN 'Error updating salary: ' || SQLERRM; END; EXCEPTION WHEN NO_DATA_FOUND THEN RETURN 'Employee not found'; WHEN OTHERS THEN RETURN 'Error: ' || SQLERRM; END; END; $$ LANGUAGE plpgsql; -- 输出结果 CREATE FUNCTION -- 测试嵌套异常处理 SELECT fgedu_exception_nested(1, 9000.00); -- 输出结果 fgedu_exception_nested ------------------------ Salary updated for 张三 (1 row) SELECT fgedu_exception_nested(1, -1000.00); -- 输出结果 fgedu_exception_nested ------------------------ Error updating salary: Salary cannot be negative (1 row) SELECT fgedu_exception_nested(999, 8000.00); -- 输出结果 fgedu_exception_nested ------------------------ Employee not found (1 row)

3.3 PostgreSQL数据库事务实现

3.3.1 事务实现

— 事务实现

— 基本事务
CREATE OR REPLACE FUNCTION fgedu_transaction_basic()
RETURNS TEXT
AS $$
BEGIN
— 开始事务
BEGIN
— 插入数据
INSERT INTO fgedu_employees(name, department, salary)
VALUES(‘吴九’, ‘技术部’, 8500.00);

— 更新数据
UPDATE fgedu_employees SET salary = salary + 500.00 WHERE department = ‘技术部’;

— 提交事务
RETURN ‘Transaction completed successfully’;
EXCEPTION
WHEN OTHERS THEN
— 回滚事务
RETURN ‘Transaction failed: ‘ || SQLERRM;
END;
END;
$$ LANGUAGE plpgsql;

— 输出结果
CREATE FUNCTION

— 测试基本事务
SELECT fgedu_transaction_basic();

— 输出结果
fgedu_transaction_basic
————————-
Transaction completed successfully
(1 row)

— 查看结果
SELECT name, department, salary FROM fgedu_employees WHERE department = ‘技术部’;

— 输出结果
name | department | salary
——+————+——–
周八 | 技术部 | 10000.00
王五 | 技术部 | 9500.00
张三 | 技术部 | 9500.00
吴九 | 技术部 | 9000.00
(4 rows)

— 显式事务
CREATE OR REPLACE FUNCTION fgedu_transaction_explicit()
RETURNS TEXT
AS $$
BEGIN
— 开始事务
BEGIN
— 插入数据
INSERT INTO fgedu_employees(name, department, salary)
VALUES(‘郑十’, ‘销售部’, 7000.00);

— 故意引发错误
IF 1 = 1 THEN
RAISE EXCEPTION ‘Test error’;
END IF;

— 更新数据
UPDATE fgedu_employees SET salary = salary + 500.00 WHERE department = ‘销售部’;

— 提交事务
RETURN ‘Transaction completed successfully’;
EXCEPTION
WHEN OTHERS THEN
— 回滚事务
RETURN ‘Transaction rolled back: ‘ || SQLERRM;
END;
END;
$$ LANGUAGE plpgsql;

— 输出结果
CREATE FUNCTION

— 测试显式事务
SELECT fgedu_transaction_explicit();

— 输出结果
fgedu_transaction_explicit
—————————-
Transaction rolled back: Test error
(1 row)

— 查看结果(郑十应该不存在)
SELECT name, department, salary FROM fgedu_employees WHERE name = ‘郑十’;

— 输出结果
name | department | salary
——+————+——–
(0 rows)

— 保存点
CREATE OR REPLACE FUNCTION fgedu_transaction_savepoint()
RETURNS TEXT
AS $$
BEGIN
— 开始事务
BEGIN
— 插入数据
INSERT INTO fgedu_employees(name, department, salary)
VALUES(‘郑十’, ‘销售部’, 7000.00);

— 创建保存点
SAVEPOINT sp1;

— 尝试更新数据
BEGIN
UPDATE fgedu_employees SET salary = salary + 500.00 WHERE department = ‘销售部’;
EXCEPTION
WHEN OTHERS THEN
— 回滚到保存点
ROLLBACK TO SAVEPOINT sp1;
RETURN ‘Updated failed, but data inserted’;
END;

— 提交事务
RETURN ‘Transaction completed successfully’;
EXCEPTION
WHEN OTHERS THEN
— 回滚事务
RETURN ‘Transaction failed: ‘ || SQLERRM;
END;
END;
$$ LANGUAGE plpgsql;

— 输出结果
CREATE FUNCTION

— 测试保存点
SELECT fgedu_transaction_savepoint();

— 输出结果
fgedu_transaction_savepoint
—————————–
Transaction completed successfully
(1 row)

— 查看结果
SELECT name, department, salary FROM fgedu_employees WHERE name = ‘郑十’;

— 输出结果
name | department | salary
——+————+——–
郑十 | 销售部 | 7500.00
(1 row)

Part04-生产案例与实战讲解

4.1 PostgreSQL数据库游标实战

本案例演示游标在实际生产环境中的应用。学习交流加群风哥QQ113257174。

— 游标实战

— 批量处理数据
CREATE OR REPLACE FUNCTION fgedu_batch_process()
RETURNS INTEGER
AS $$
DECLARE
cur_employees REFCURSOR;
v_employee fgedu_employees%ROWTYPE;
v_count INTEGER := 0;
BEGIN
— 打开游标
OPEN cur_employees FOR
SELECT * FROM fgedu_employees WHERE salary < 7000; -- 遍历游标 LOOP FETCH cur_employees INTO v_employee; EXIT WHEN NOT FOUND; -- 处理数据 UPDATE fgedu_employees SET salary = salary * 1.1 WHERE id = v_employee.id; v_count := v_count + 1; END LOOP; -- 关闭游标 CLOSE cur_employees; RETURN v_count; END; $$ LANGUAGE plpgsql; -- 输出结果 CREATE FUNCTION -- 测试批量处理 SELECT fgedu_batch_process(); -- 输出结果 fgedu_batch_process -------------------- 2 (1 row) -- 查看处理结果 SELECT name, department, salary FROM fgedu_employees WHERE salary < 7000; -- 输出结果 name | department | salary ------+------------+-------- (0 rows) -- 分页查询 CREATE OR REPLACE FUNCTION fgedu_paginate_employees( IN p_page INTEGER, IN p_page_size INTEGER ) RETURNS TABLE( id INTEGER, name VARCHAR(100), department VARCHAR(100), salary NUMERIC(10,2) ) AS $$ DECLARE v_offset INTEGER := (p_page - 1) * p_page_size; BEGIN RETURN QUERY SELECT id, name, department, salary FROM fgedu_employees ORDER BY id LIMIT p_page_size OFFSET v_offset; END; $$ LANGUAGE plpgsql; -- 输出结果 CREATE FUNCTION -- 测试分页查询 SELECT * FROM fgedu_paginate_employees(1, 3); -- 输出结果 id | name | department | salary ----+------+------------+-------- 1 | 张三 | 技术部 | 9500.00 2 | 李四 | 销售部 | 6600.00 3 | 王五 | 技术部 | 9500.00 (3 rows) SELECT * FROM fgedu_paginate_employees(2, 3); -- 输出结果 id | name | department | salary ----+------+------------+-------- 4 | 赵六 | 财务部 | 7000.00 5 | 孙七 | 销售部 | 7150.00 6 | 周八 | 技术部 | 10000.00 (3 rows) -- 游标与动态SQL CREATE OR REPLACE FUNCTION fgedu_cursor_dynamic(IN p_condition TEXT) RETURNS TEXT AS $$ DECLARE cur_employees REFCURSOR; v_employee fgedu_employees%ROWTYPE; v_result TEXT := ''; v_sql TEXT; BEGIN -- 构建动态SQL v_sql := 'SELECT * FROM fgedu_employees'; IF p_condition IS NOT NULL THEN v_sql := v_sql || ' WHERE ' || p_condition; END IF; v_sql := v_sql || ' ORDER BY salary DESC'; -- 打开游标 OPEN cur_employees FOR EXECUTE v_sql; -- 遍历游标 LOOP FETCH cur_employees INTO v_employee; EXIT WHEN NOT FOUND; v_result := v_result || v_employee.name || ' (' || v_employee.salary || '), '; END LOOP; -- 关闭游标 CLOSE cur_employees; -- 处理结果 v_result := rtrim(v_result, ', '); RETURN v_result; END; $$ LANGUAGE plpgsql; -- 输出结果 CREATE FUNCTION -- 测试动态游标 SELECT fgedu_cursor_dynamic('department = ''技术部'''); -- 输出结果 fgedu_cursor_dynamic ------------------------------------ 周八 (10000.00), 张三 (9500.00), 王五 (9500.00), 吴九 (9000.00) (1 row)

4.2 PostgreSQL数据库异常处理实战

本案例演示异常处理在实际生产环境中的应用。更多学习教程公众号风哥教程itpux_com。

— 异常处理实战

— 数据验证
CREATE OR REPLACE FUNCTION fgedu_validate_employee(
IN p_name VARCHAR(100),
IN p_department VARCHAR(100),
IN p_salary NUMERIC(10,2)
)
RETURNS INTEGER
AS $$
DECLARE
v_employee_id INTEGER;
BEGIN
BEGIN
— 验证数据
IF p_name IS NULL OR p_name = ” THEN
RAISE EXCEPTION ‘Name cannot be empty’;
END IF;

IF p_department IS NULL OR p_department = ” THEN
RAISE EXCEPTION ‘Department cannot be empty’;
END IF;

IF p_salary <= 0 THEN RAISE EXCEPTION 'Salary must be positive'; END IF; -- 插入数据 INSERT INTO fgedu_employees(name, department, salary) VALUES(p_name, p_department, p_salary) RETURNING id INTO v_employee_id; RETURN v_employee_id; EXCEPTION WHEN OTHERS THEN RAISE EXCEPTION 'Error creating employee: %', SQLERRM; END; END; $$ LANGUAGE plpgsql; -- 输出结果 CREATE FUNCTION -- 测试数据验证 SELECT fgedu_validate_employee('陈十一', '技术部', 8000.00); -- 输出结果 fgedu_validate_employee ------------------------ 8 (1 row) SELECT fgedu_validate_employee('', '技术部', 8000.00); -- 输出结果 ERROR: Error creating employee: Name cannot be empty -- 错误日志记录 CREATE TABLE fgedu_error_logs ( id SERIAL PRIMARY KEY, function_name VARCHAR(100) NOT NULL, error_message TEXT NOT NULL, error_time TIMESTAMP DEFAULT NOW() ); -- 输出结果 CREATE TABLE CREATE OR REPLACE FUNCTION fgedu_log_error( IN p_function_name VARCHAR(100), IN p_error_message TEXT ) RETURNS VOID AS $$ BEGIN INSERT INTO fgedu_error_logs(function_name, error_message) VALUES(p_function_name, p_error_message); END; $$ LANGUAGE plpgsql; -- 输出结果 CREATE FUNCTION CREATE OR REPLACE FUNCTION fgedu_safe_operation( IN p_id INTEGER, IN p_new_salary NUMERIC(10,2) ) RETURNS TEXT AS $$ BEGIN BEGIN -- 执行操作 UPDATE fgedu_employees SET salary = p_new_salary WHERE id = p_id; IF NOT FOUND THEN RAISE EXCEPTION 'Employee not found'; END IF; RETURN 'Operation completed successfully'; EXCEPTION WHEN OTHERS THEN -- 记录错误 PERFORM fgedu_log_error('fgedu_safe_operation', SQLERRM); RETURN 'Operation failed: ' || SQLERRM; END; END; $$ LANGUAGE plpgsql; -- 输出结果 CREATE FUNCTION -- 测试错误日志 SELECT fgedu_safe_operation(999, 8000.00); -- 输出结果 fgedu_safe_operation ---------------------- Operation failed: Employee not found (1 row) -- 查看错误日志 SELECT * FROM fgedu_error_logs; -- 输出结果 id | function_name | error_message | error_time ----+-------------------+------------------+---------------------------- 1 | fgedu_safe_operation | Employee not found | 2026-04-08 03:00:00 (1 row)

4.3 PostgreSQL数据库事务实战

本案例演示事务在实际生产环境中的应用。from PostgreSQL视频:www.itpux.com。

— 事务实战

— 订单处理
CREATE TABLE fgedu_orders (
id SERIAL PRIMARY KEY,
customer_id INTEGER NOT NULL,
total_amount NUMERIC(10,2) NOT NULL,
status VARCHAR(20) DEFAULT ‘pending’,
created_at TIMESTAMP DEFAULT NOW()
);

— 输出结果
CREATE TABLE

CREATE TABLE fgedu_order_items (
id SERIAL PRIMARY KEY,
order_id INTEGER REFERENCES fgedu_orders(id),
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
price NUMERIC(10,2) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);

— 输出结果
CREATE TABLE

CREATE TABLE fgedu_products (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price NUMERIC(10,2) NOT NULL,
stock INTEGER NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);

— 输出结果
CREATE TABLE

— 插入产品数据
INSERT INTO fgedu_products(name, price, stock)
VALUES
(‘iPhone 15’, 7999.00, 100),
(‘iPad Air’, 4999.00, 50),
(‘MacBook Pro’, 14999.00, 30);

— 输出结果
INSERT 0 3

— 订单处理函数
CREATE OR REPLACE FUNCTION fgedu_process_order(
IN p_customer_id INTEGER,
IN p_items JSONB
)
RETURNS INTEGER
AS $$
DECLARE
v_order_id INTEGER;
v_item JSONB;
v_product_id INTEGER;
v_quantity INTEGER;
v_price NUMERIC(10,2);
v_total_amount NUMERIC(10,2) := 0;
v_product_stock INTEGER;
BEGIN
— 开始事务
BEGIN
— 创建订单
INSERT INTO fgedu_orders(customer_id, total_amount, status)
VALUES(p_customer_id, 0, ‘pending’)
RETURNING id INTO v_order_id;

— 处理订单详情
FOR v_item IN SELECT * FROM jsonb_array_elements(p_items) LOOP
v_product_id := (v_item->>’product_id’)::INTEGER;
v_quantity := (v_item->>’quantity’)::INTEGER;

— 获取产品信息
SELECT price, stock INTO v_price, v_product_stock
FROM fgedu_products
WHERE id = v_product_id;

— 检查库存
IF v_product_stock < v_quantity THEN RAISE EXCEPTION 'Insufficient stock for product %', v_product_id; END IF; -- 插入订单详情 INSERT INTO fgedu_order_items(order_id, product_id, quantity, price) VALUES(v_order_id, v_product_id, v_quantity, v_price); -- 计算总金额 v_total_amount := v_total_amount + (v_price * v_quantity); -- 更新库存 UPDATE fgedu_products SET stock = stock - v_quantity WHERE id = v_product_id; END LOOP; -- 更新订单总金额 UPDATE fgedu_orders SET total_amount = v_total_amount WHERE id = v_order_id; -- 提交事务 RETURN v_order_id; EXCEPTION WHEN OTHERS THEN -- 回滚事务 RAISE EXCEPTION 'Error processing order: %', SQLERRM; END; END; $$ LANGUAGE plpgsql; -- 输出结果 CREATE FUNCTION -- 测试订单处理 SELECT fgedu_process_order(1, '[{ "product_id": 1, "quantity": 2 }, { "product_id": 2, "quantity": 1 }]'); -- 输出结果 fgedu_process_order -------------------- 1 (1 row) -- 查看订单 SELECT * FROM fgedu_orders; -- 输出结果 id | customer_id | total_amount | status | created_at ----+-------------+--------------+---------+---------------------------- 1 | 1 | 20997.00| pending | 2026-04-08 03:15:00 (1 row) -- 查看订单详情 SELECT * FROM fgedu_order_items; -- 输出结果 id | order_id | product_id | quantity | price | created_at ----+----------+------------+----------+--------+---------------------------- 1 | 1 | 1 | 2 | 7999.00| 2026-04-08 03:15:00 2 | 1 | 2 | 1 | 4999.00| 2026-04-08 03:15:00 (2 rows) -- 查看库存 SELECT id, name, stock FROM fgedu_products; -- 输出结果 id | name | stock ----+-----------+------- 1 | iPhone 15 | 98 2 | iPad Air | 49 3 | MacBook Pro| 30 (3 rows) -- 测试事务回滚 SELECT fgedu_process_order(1, '[{ "product_id": 1, "quantity": 1000 }, { "product_id": 2, "quantity": 1 }]'); -- 输出结果 ERROR: Error processing order: Insufficient stock for product 1 -- 查看订单(应该没有新订单) SELECT * FROM fgedu_orders; -- 输出结果 id | customer_id | total_amount | status | created_at ----+-------------+--------------+---------+---------------------------- 1 | 1 | 20997.00| pending | 2026-04-08 03:15:00 (1 row) -- 查看库存(应该没有变化) SELECT id, name, stock FROM fgedu_products; -- 输出结果 id | name | stock ----+-----------+------- 1 | iPhone 15 | 98 2 | iPad Air | 49 3 | MacBook Pro| 30 (3 rows)

Part05-风哥经验总结与分享

5.1 PostgreSQL数据库最佳实践

最佳实践:

  • 游标使用:只在必要时使用游标,避免频繁使用;合理设置游标参数;及时关闭游标;处理大量数据时使用游标;监控游标执行情况。
  • 异常处理:捕获特定异常,避免捕获所有异常;提供详细的错误信息;在适当的地方处理异常;避免过度使用异常;记录异常信息。
  • 事务管理:保持事务简短,减少锁定时间;合理设置事务隔离级别;避免长事务;使用批量操作,减少事务次数;监控事务执行情况。
  • 代码质量:保持代码逻辑清晰;使用适当的命名规范;添加注释;定期重构代码;使用版本控制。
  • 性能优化:优化查询语句;使用索引;避免不必要的计算;合理使用缓存;监控性能指标。
开发建议:

  • 使用游标处理大量数据时,注意内存使用
  • 异常处理要详细,便于排查问题
  • 事务要保持简短,避免长时间锁定
  • 定期监控函数和存储过程的执行性能
  • 遵循PostgreSQL的编码规范

5.2 PostgreSQL数据库常见问题

常见问题:

  • 游标问题:游标未关闭,导致资源泄漏;游标处理大量数据时内存不足;游标执行效率低下。
  • 异常处理问题:异常捕获过于宽泛;异常信息不详细;异常处理嵌套过深。
  • 事务问题:事务过长,导致锁定时间过长;事务隔离级别设置不当;事务嵌套过深。
  • 性能问题:查询语句效率低下;游标使用不当;事务处理不当。
  • 维护问题:代码难以维护;缺乏注释;版本管理混乱。

5.3 PostgreSQL数据库故障排查

故障排查:

  • 游标问题:检查游标是否正确关闭;监控游标执行情况;优化游标查询语句。
  • 异常处理问题:查看PostgreSQL日志;分析错误信息;优化异常处理逻辑。
  • 事务问题:查看事务状态;分析锁等待情况;优化事务逻辑。
  • 性能问题:使用EXPLAIN分析执行计划;监控系统性能指标;优化查询语句。
  • 维护问题:重构代码;添加注释;建立版本管理机制。
— 监控和管理示例

— 查看游标使用情况
SELECT
pid,
usename,
fgapplication_name,
state,
query
FROM pg_stat_activity
WHERE state = ‘active’;

— 输出结果
pid | usename | fgapplication_name | state | query
——+———+——————+——–+——————————————————————–
1234 | postgres| psql | active | SELECT * FROM fgedu_cursor_basic();

— 查看事务状态
SELECT
pid,
usename,
fgapplication_name,
state,
xact_start,
query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL;

— 输出结果
pid | usename | fgapplication_name | state | xact_start | query
——+———+——————+——–+——————————-+——————————————————————–
1234 | postgres| psql | active | 2026-04-08 03:30:00.123456 | SELECT fgedu_process_order(1, ‘[{“product_id”: 1, “quantity”: 2}]’);

— 查看锁情况
SELECT
locktype,
database,
relation,
mode,
granted,
pid
FROM pg_locks
WHERE pid = 1234;

— 输出结果
locktype | database | relation | mode | granted | pid
———-+———-+———-+——————+———+——
relation | 12345 | 67890 | RowExclusiveLock | t | 1234

— 查看函数执行情况
SELECT
proname AS function_name,
calls AS call_count,
total_time AS total_execution_time,
mean_time AS average_execution_time
FROM pg_stat_user_functions
WHERE proname LIKE ‘fgedu%’;

— 输出结果
function_name | call_count | total_execution_time | average_execution_time
————————–+————+———————-+————————
fgedu_cursor_basic | 1 | 0.234 | 0.234
fgedu_cursor_with_params | 1 | 0.123 | 0.123
fgedu_exception_basic | 2 | 0.187 | 0.093
fgedu_transaction_basic | 1 | 0.345 | 0.345
fgedu_process_order | 2 | 0.567 | 0.283
(5 rows)

风哥提示:PL/pgSQL的游标、异常处理和事务是处理复杂业务逻辑的重要工具。在使用过程中,需要注意:1) 只在必要时使用游标,避免性能问题;2) 完善异常处理机制,提高代码健壮性;3) 合理设计事务逻辑,确保数据一致性;4) 定期监控和优化代码执行性能;5) 遵循最佳实践,提高代码质量。通过合理使用这些特性,可以编写更加可靠和高效的数据库应用。

本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html

联系我们

在线咨询:点击这里给我发消息

微信号:itpux-com

工作日:9:30-18:30,节假日休息