多步骤过程就像数据库里的“瑞士军刀”。它们通常包括输入数据校验、执行变更(比如更新记录、插入日志),有时候还带点分析功能。但问题来了:过程越复杂,出错的概率就越大。逻辑bug、慢查询、遗漏的小细节——一不小心就全乱套了。
综合调试主要包括这些方面:
- 分析输入数据:参数设置对吗?传进来的数据靠谱吗?
- 检查关键步骤的执行:每一步都顺利跑了吗?
- 记录中间结果日志:这样你能知道在“炸了”之前发生了啥。
- 优化性能瓶颈:搞定那些拖慢查询的“短板”。
任务描述:多步骤过程的例子
举个实际例子,假设我们在搞一个网店的数据库。现在要写个处理订单的过程。它要做这些事:
- 检查商品库存。
- 预留商品。
- 更新订单状态。
- 把事件(比如预留成功或报错)写进日志表。
数据库结构脚本:
-- 商品表
CREATE TABLE products (
product_id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
stock_quantity INTEGER NOT NULL
);
-- 订单表
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
product_id INTEGER REFERENCES products(product_id),
order_status TEXT NOT NULL
);
-- 日志表
CREATE TABLE order_logs (
log_id SERIAL PRIMARY KEY,
order_id INTEGER,
log_message TEXT,
log_time TIMESTAMP DEFAULT NOW()
);
第1步:创建多步骤过程
来写个基础的process_order过程。它会接收订单ID,然后一步步处理订单。
CREATE OR REPLACE FUNCTION process_order(p_order_id INTEGER)
RETURNS VOID LANGUAGE plpgsql AS $$
DECLARE
v_product_id INTEGER;
v_stock_quantity INTEGER;
BEGIN
-- 1. 获取商品ID和订单状态
SELECT product_id INTO v_product_id
FROM orders
WHERE order_id = p_order_id;
IF v_product_id IS NULL THEN
RAISE EXCEPTION '订单 % 不存在或缺少 product_id', p_order_id;
END IF;
-- 2. 检查商品库存
SELECT stock_quantity INTO v_stock_quantity
FROM products
WHERE product_id = v_product_id;
IF v_stock_quantity <= 0 THEN
RAISE EXCEPTION '商品 % 库存不足', v_product_id;
END IF;
-- 3. 更新库存数量
UPDATE products
SET stock_quantity = stock_quantity - 1
WHERE product_id = v_product_id;
-- 4. 更新订单状态
UPDATE orders
SET order_status = '已处理'
WHERE order_id = p_order_id;
-- 5. 写入成功事件到日志
INSERT INTO order_logs(order_id, log_message)
VALUES (p_order_id, '订单处理成功。');
END;
$$;
第2步:用RAISE NOTICE和RAISE EXCEPTION记录错误日志
这才是“魔法”开始的地方。我们加点中间步骤的日志,这样出错时能知道每一步发生了啥。
加了日志的代码:
CREATE OR REPLACE FUNCTION process_order(p_order_id INTEGER)
RETURNS VOID LANGUAGE plpgsql AS $$
DECLARE
v_product_id INTEGER;
v_stock_quantity INTEGER;
BEGIN
RAISE NOTICE '正在处理订单 %...', p_order_id;
-- 1. 获取商品ID
SELECT product_id INTO v_product_id
FROM orders
WHERE order_id = p_order_id;
IF v_product_id IS NULL THEN
RAISE EXCEPTION '订单 % 不存在或缺少 product_id', p_order_id;
END IF;
RAISE NOTICE '订单 % 的商品ID: %', p_order_id, v_product_id;
-- 2. 检查商品库存
SELECT stock_quantity INTO v_stock_quantity
FROM products
WHERE product_id = v_product_id;
IF v_stock_quantity <= 0 THEN
RAISE EXCEPTION '商品 % 库存不足', v_product_id;
END IF;
RAISE NOTICE '商品 % 的库存数量: %', v_product_id, v_stock_quantity;
-- 3. 更新库存数量
UPDATE products
SET stock_quantity = stock_quantity - 1
WHERE product_id = v_product_id;
-- 4. 更新订单状态
UPDATE orders
SET order_status = '已处理'
WHERE order_id = p_order_id;
-- 5. 日志记录成功
INSERT INTO order_logs(order_id, log_message)
VALUES (p_order_id, '订单处理成功。');
RAISE NOTICE '订单 % 处理成功。', p_order_id;
EXCEPTION WHEN OTHERS THEN
-- 记录错误日志
INSERT INTO order_logs(order_id, log_message)
VALUES (p_order_id, '错误: ' || SQLERRM);
RAISE;
END;
$$;
第3步:用索引优化
如果数据库里商品或订单很多,查找目标行就可能成瓶颈。加点索引,让处理更快:
-- 给orders表加索引,加速查找
CREATE INDEX idx_orders_product_id ON orders(product_id);
-- 给products表加索引,加速查找
CREATE INDEX idx_products_stock_quantity ON products(stock_quantity);
第4步:用EXPLAIN ANALYZE分析性能
现在来看看我们的函数到底跑得有多快。用性能分析工具试试:
EXPLAIN ANALYZE
SELECT process_order(1);
结果会显示每一步花了多少时间。你能找出最慢的步骤,然后继续优化过程。
第5步:用事务提升可靠性
为了更靠谱,可以把整个过程包进事务里。这样一旦出错,所有更改都会回滚。
BEGIN;
-- 调用函数
SELECT process_order(1);
-- 提交事务
COMMIT;
在函数代码里也可以用SAVEPOINT和ROLLBACK TO SAVEPOINT来处理部分错误。
实战任务:批量处理订单
最后来个例子,批量处理多个订单。我们写个函数,把所有Pending状态的订单都处理掉:
CREATE OR REPLACE FUNCTION process_all_orders()
RETURNS VOID LANGUAGE plpgsql AS $$
DECLARE
v_order_id INTEGER;
BEGIN
FOR v_order_id IN
SELECT order_id
FROM orders
WHERE order_status = 'Pending'
LOOP
BEGIN
PERFORM process_order(v_order_id);
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE '处理订单 % 失败: %', v_order_id, SQLERRM;
END;
END LOOP;
END;
$$;
调用这个函数时,所有Pending状态的订单都会被处理,出错的只会记录日志,不会影响其他订单。
所以,这节课我们演示了怎么调试和优化复杂过程,让它们更可靠、更高效、更易读。这些技能在真实项目里超有用,过程质量直接影响应用的成败。
GO TO FULL VERSION