一、为什么需要存储过程、函数与触发器?

当业务变得越来越复杂,单纯靠应用程序代码处理所有逻辑会显得力不从心。想象一下,每次用户下单都需要在多个表里插入数据、更新库存、计算折扣,如果这些操作都在应用层完成,不仅代码臃肿,性能也会受影响。这时候,数据库层面的逻辑处理能力就显得尤为重要。

PolarDB作为阿里云推出的云原生数据库,提供了强大的存储过程、函数和触发器支持。它们就像是预装在数据库里的"小程序",可以帮我们完成很多重复性工作。举个例子,电商平台的订单状态变更时自动发送通知,这种场景用触发器就再合适不过了。

二、存储过程:数据库里的"批处理脚本"

存储过程像是一个预编译好的脚本,可以接受参数,包含一系列SQL语句和控制逻辑。它的最大优势是减少网络传输,因为所有操作都在数据库内部完成。

-- 技术栈:PolarDB PostgreSQL兼容版
-- 创建一个处理订单的存储过程
CREATE OR REPLACE PROCEDURE process_order(
    p_user_id INT,
    p_product_id INT,
    p_quantity INT
)
LANGUAGE plpgsql
AS $$
DECLARE
    v_stock INT;
    v_price DECIMAL(10,2);
    v_order_id INT;
BEGIN
    -- 检查库存
    SELECT stock, price INTO v_stock, v_price 
    FROM products WHERE id = p_product_id;
    
    IF v_stock < p_quantity THEN
        RAISE EXCEPTION '库存不足,当前库存: %', v_stock;
    END IF;
    
    -- 创建订单
    INSERT INTO orders(user_id, order_date, status)
    VALUES (p_user_id, CURRENT_TIMESTAMP, '待付款')
    RETURNING id INTO v_order_id;
    
    -- 添加订单明细
    INSERT INTO order_items(order_id, product_id, quantity, unit_price)
    VALUES (v_order_id, p_product_id, p_quantity, v_price);
    
    -- 更新库存
    UPDATE products SET stock = stock - p_quantity 
    WHERE id = p_product_id;
    
    COMMIT;
    RAISE NOTICE '订单 % 创建成功', v_order_id;
END;
$$;

-- 调用存储过程
CALL process_order(123, 456, 2);

这个例子展示了存储过程的典型用法:封装业务逻辑、减少网络往返、保证操作原子性。适合用在订单处理、数据批量导入等场景。

三、函数:可重用的计算单元

函数和存储过程很像,但主要区别是函数必须有返回值,且通常不修改数据库状态。它们特别适合封装复杂计算逻辑。

-- 技术栈:PolarDB PostgreSQL兼容版
-- 创建一个计算折扣价格的函数
CREATE OR REPLACE FUNCTION calculate_discount(
    original_price DECIMAL(10,2),
    user_level VARCHAR(20),
    promotion_active BOOLEAN
) 
RETURNS DECIMAL(10,2)
LANGUAGE plpgsql
AS $$
DECLARE
    discount_rate DECIMAL(3,2);
    final_price DECIMAL(10,2);
BEGIN
    -- 基础折扣率
    discount_rate := 1.0;
    
    -- 根据用户等级调整
    CASE user_level
        WHEN 'VIP' THEN discount_rate := 0.85;
        WHEN 'GOLD' THEN discount_rate := 0.9;
        WHEN 'SILVER' THEN discount_rate := 0.95;
        ELSE discount_rate := 1.0;
    END CASE;
    
    -- 促销活动额外折扣
    IF promotion_active THEN
        discount_rate := discount_rate * 0.95;
    END IF;
    
    -- 计算最终价格
    final_price := original_price * discount_rate;
    
    -- 确保不低于成本价
    IF final_price < original_price * 0.6 THEN
        final_price := original_price * 0.6;
    END IF;
    
    RETURN ROUND(final_price, 2);
END;
$$;

-- 使用函数
SELECT calculate_discount(100.00, 'VIP', true);

函数非常适合价格计算、数据转换、条件判断等场景。在PolarDB中,函数还可以用在SQL语句的SELECT、WHERE等子句中,非常灵活。

四、触发器:自动化的数据库"看门人"

触发器是在特定数据库事件(INSERT/UPDATE/DELETE)发生时自动执行的特殊存储过程。它们像是数据库的"自动应答机"。

-- 技术栈:PolarDB PostgreSQL兼容版
-- 创建一个订单日志触发器
CREATE OR REPLACE FUNCTION log_order_changes()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
    IF TG_OP = 'INSERT' THEN
        INSERT INTO order_audit_log(
            order_id, 
            action, 
            change_time,
            new_status
        )
        VALUES (
            NEW.id, 
            '创建订单', 
            CURRENT_TIMESTAMP,
            NEW.status
        );
    ELSIF TG_OP = 'UPDATE' THEN
        IF NEW.status <> OLD.status THEN
            INSERT INTO order_audit_log(
                order_id, 
                action, 
                change_time,
                old_status,
                new_status
            )
            VALUES (
                NEW.id, 
                '更新订单状态', 
                CURRENT_TIMESTAMP,
                OLD.status,
                NEW.status
            );
        END IF;
    ELSIF TG_OP = 'DELETE' THEN
        INSERT INTO order_audit_log(
            order_id, 
            action, 
            change_time
        )
        VALUES (
            OLD.id, 
            '删除订单', 
            CURRENT_TIMESTAMP
        );
    END IF;
    RETURN NEW;
END;
$$;

-- 在orders表上创建触发器
CREATE TRIGGER order_audit_trigger
AFTER INSERT OR UPDATE OR DELETE ON orders
FOR EACH ROW
EXECUTE FUNCTION log_order_changes();

触发器最适合用于审计日志、数据同步、业务规则验证等场景。但要注意,过多或过于复杂的触发器会影响性能。

五、高级特性与性能优化

PolarDB对这些特性做了很多优化。比如存储过程支持预编译,执行计划会被缓存;函数可以标记为IMMUTABLE(不依赖数据库状态)或STABLE(结果在事务内不变),帮助优化器生成更好的执行计划。

-- 技术栈:PolarDB PostgreSQL兼容版
-- 创建一个优化标记的函数
CREATE OR REPLACE FUNCTION format_phone_number(phone TEXT)
RETURNS TEXT
LANGUAGE sql
IMMUTABLE  -- 标记为不可变函数
AS $$
    SELECT regexp_replace(phone, '(\d{3})(\d{4})(\d{4})', '\1****\3');
$$;

-- 这个函数可以被优化器内联处理
SELECT * FROM users WHERE format_phone_number(phone) = '138****1234';

对于频繁调用的简单逻辑,还可以使用SQL语言函数,它们通常比PL/pgSQL函数执行效率更高。

六、应用场景与技术选型建议

  1. 存储过程最适合:

    • 复杂的多步数据操作
    • 需要事务控制的批量处理
    • 对性能敏感的核心业务逻辑
  2. 函数最适合:

    • 数据转换和计算
    • 可重用的业务规则
    • SQL查询中的复杂条件
  3. 触发器最适合:

    • 数据变更审计
    • 跨表数据同步
    • 简单的业务规则实施

注意事项:

  • 避免在触发器中执行耗时操作
  • 复杂的业务逻辑优先考虑用存储过程
  • 函数尽量设计为无副作用
  • 注意错误处理和事务控制

七、总结

PolarDB的存储过程、函数和触发器为处理复杂业务逻辑提供了强大工具。它们能让数据操作更高效,业务逻辑更集中,代码更简洁。合理使用这些特性,可以显著提升应用性能和可维护性。

记住,没有银弹。存储过程适合封装数据库密集型的操作,但不应滥用。现代应用架构中,业务逻辑的放置位置需要综合考虑性能、维护性和团队技能等因素。PolarDB提供的这些特性给了我们更多选择,让数据库不再是简单的数据存储,而是能主动参与业务处理的智能数据平台。