在数据库开发中,SQL存储函数是一段预先编译并保存在服务端的程序逻辑,可以被多条SQL语句重复调用。合理使用存储函数能够简化业务代码、统一计算规则,但如果忽略执行机制,也容易造成全表扫描和大量重复计算。下面从实战角度说明存储函数如何编写以及怎样避免性能损耗。

一、存储函数的基础写法
以MySQL为例,一个简单的计算订单折扣后金额的存储函数如下:
DELIMITER //
CREATE FUNCTION calc_final_price(origin DECIMAL(10,2), discount_rate DECIMAL(3,2))
RETURNS DECIMAL(10,2)
DETERMINISTIC
BEGIN
-- 如果折扣为空则按原价返回
IF discount_rate IS NULL THEN
RETURN origin;
END IF;
RETURN origin * discount_rate;
END //
DELIMITER ;
在查询中可以直接调用:
SELECT order_id, calc_final_price(amount, rate) AS final_price FROM orders;
二、存储函数引发的性能问题
1. 在WHERE子句中调用函数
当函数在WHERE条件中对索引列进行包裹时,数据库往往无法使用索引,例如:
-- 错误示范:索引失效 SELECT * FROM user WHERE YEAR(create_time) = 2023;
应改为范围查询以利用create_time上的索引:
SELECT * FROM user WHERE create_time >= '2023-01-01' AND create_time < '2024-01-01';
2. 函数内部嵌套复杂查询
若存储函数内部包含SELECT且被外层逐行调用,会产生大量重复IO。此时应改为关联查询或物化视图。
三、提升性能的实战方法
- 将确定性函数标记为
DETERMINISTIC,帮助优化器缓存结果 - 避免在索引列上包裹函数,改写SQL以命中索引
- 批量处理场景用集合操作替代逐行函数调用
- 对频繁使用的计算结果建立冗余字段或汇总表
四、何时不该使用存储函数
以下情况建议改用普通SQL或视图:
| 场景 | 推荐方案 |
|---|---|
| 简单字段拼接 | 直接使用CONCAT |
| 跨表聚合 | 使用视图或CTE |
| 高频过滤条件 | 改写为索引友好SQL |
示例:用视图替代函数嵌套
CREATE VIEW v_order_price AS SELECT order_id, amount * COALESCE(rate, 1) AS final_price FROM orders;
通过上述方式,既能保证逻辑清晰,也能让查询计划更稳定。存储函数本身不是性能瓶颈,关键在于调用位置与数据规模是否匹配。