SQL Server 存储过程开发手册(更新版)
根据要求,重新整理并加入了事务控制、异常日志记录和返回状态码的设计。以下是详细说明:
1. 总则
1.1 目标
本手册旨在为 SQL Server 存储过程的编写提供一套完整的规范,确保系统的事务控制、异常处理、日志记录和状态码返回清晰明确,优化数据一致性、系统稳定性和性能。
2. 事务控制与层次化设计
2.1 事务控制原则
根据业务的需求和事务的粒度,存储过程应分为不同的层次,每个层次的事务控制和异常处理策略不同,具体如下表所示:
层次 | 控制事务 | 记录异常日志 | 返回状态码 |
---|---|---|---|
主流程 (Main) | ❌ | ✅ | ✅ |
子事务 (SubTx) | ✅ | ✅ | ✅ |
功能性操作 (Func) | ❌ | ❌ | 可选 |
-
主流程 (Main):
-
不控制事务,负责协调和调用子事务或功能性操作。
-
记录异常日志,捕获并输出错误信息。
-
返回状态码,以通知调用方执行状态(成功或失败)。
-
-
子事务 (SubTx):
-
控制事务,开始和提交或回滚事务。
-
记录异常日志,在发生异常时捕获并输出错误信息。
-
返回状态码,标识事务是否成功完成。
-
-
功能性操作 (Func):
-
不控制事务,通常是读取操作或不涉及数据修改的辅助功能。
-
不记录异常日志(除非必要),通常这些操作在主流程中捕获异常。
-
返回状态码可选,根据具体业务需要决定是否返回。
-
3. 存储过程命名规范
存储过程命名应该遵循以下结构,便于识别和维护:
proc_领域_子领域_类型
-
领域 (Domain):表示存储过程所属的业务领域,如订单、用户、商品等。
-
子领域 (SubDomain):表示存储过程在领域中的具体细分模块,如发货、支付、库存等。
-
类型 (Type):表示存储过程的类型,分为三种类型:
-
Main
:主流程,负责协调其他子事务和功能性操作。 -
SubTx
:子事务,负责数据更新和事务控制。 -
Func
:功能性操作,通常是查询或计算操作。
-
3.1 示例命名
-
proc_Order_Shipment_SubTx
:表示"订单"领域中的"发货"子领域的子事务,涉及事务控制。 -
proc_Order_Payment_Main
:表示"订单"领域中的"支付"子领域的主流程,负责调用其他子事务。 -
proc_Inventory_Check_Func
:表示"库存"领域中的"检查"功能,通常用于查询操作,不涉及事务控制。
4. 存储过程设计与事务控制
4.1 主流程存储过程
主流程存储过程不涉及事务控制,它的职责是调用子事务和功能性操作,并记录异常日志和返回执行状态码。
CREATE PROCEDURE proc_Order_Shipment_Main
@OrderId INT,
@code INT output,
@msg varchar(500) output
AS
BEGIN
BEGIN TRY
-- 调用子事务,子事务控制事务
EXEC proc_Order_Shipment_SubTx @OrderId, @code,@msg ;
if(@code!=200){
-- 记录失败原因,处理失败带来的影响
return
}
-- 调用功能性操作(如果需要)
EXEC proc_Inventory_Check_Func @OrderId, @code,@msg ;
if(@code!=200){
-- 记录失败原因,处理失败带来的影响
return
}
-- 返回成功状态码
set @code=200
END TRY
BEGIN CATCH
-- 捕获并记录异常日志
PRINT 'Error in proc_Order_Shipment_Main: ' + ERROR_MESSAGE();
-- 返回失败状态码
RETURN 1; -- 状态码:1 表示失败
END CATCH
END;
4.2 子事务存储过程
子事务存储过程负责控制事务,它需要开始、提交和回滚事务,并在发生异常时记录日志和返回状态码。
CREATE PROCEDURE proc_Order_Shipment_SubTx
@OrderId INT,
@code INT output,
@msg varchar(500) output
AS
BEGIN
-- 开始事务
BEGIN TRANSACTION;
BEGIN TRY
-- 订单发货相关的数据库操作
UPDATE dbo.Order
SET ShipmentStatus = 'Shipped'
WHERE OrderId = @OrderId;
-- 提交事务
COMMIT TRANSACTION;
-- 返回成功状态码
RETURN 0; -- 状态码:0 表示成功
END TRY
BEGIN CATCH
-- 发生错误时回滚事务
ROLLBACK TRANSACTION;
-- 记录异常日志
PRINT 'Error in proc_Order_Shipment_SubTx: ' + ERROR_MESSAGE();
-- 返回失败状态码
RETURN 1; -- 状态码:1 表示失败
END CATCH
END;
4.3 功能性操作存储过程
功能性操作通常是只执行查询或计算,不涉及数据修改,因此不控制事务。日志记录和状态码返回可选,具体业务需求决定。
CREATE PROCEDURE proc_Inventory_Check_Func
@OrderId INT,
@code INT output,
@msg varchar(500) output
AS
BEGIN
-- 查询操作,检查库存
SELECT ProductId, AvailableStock
FROM dbo.Inventory
WHERE ProductId IN (SELECT ProductId FROM dbo.OrderItems WHERE OrderId = @OrderId);
END;
5. 错误处理与日志记录
5.1 错误处理
-
主流程:主流程存储过程捕获所有子事务和功能性操作中的异常,但不控制事务回滚。它应当记录异常日志,并返回相应的状态码(通常是 0 或 1)。
-
子事务 :子事务存储过程必须通过
BEGIN TRY
和BEGIN CATCH
语句来捕获异常,并在异常发生时回滚事务。所有异常都应该记录日志,并返回状态码,标识事务是否成功。 -
功能性操作:功能性操作通常不捕获异常,也不记录日志,异常处理依赖于主流程。
5.2 异常日志记录
所有的异常都应通过日志记录下来,方便后期排查和优化。可以使用 PRINT
或 RAISEERROR
语句来记录错误消息。
BEGIN CATCH
PRINT 'Error: ' + ERROR_MESSAGE();
-- 或者使用 RAISEERROR 记录更详细的日志
-- RAISEERROR('Error in procedure: %s', 16, 1, ERROR_MESSAGE());
THROW;
END CATCH
6. 返回状态码
每个存储过程在执行完毕后应返回一个状态码,状态码通常为:
-
0
:表示操作成功。 -
1
:表示操作失败(可根据需要扩展更多状态码,表示不同类型的错误)。
返回状态码用于调用方判断存储过程执行是否成功,并做相应的处理。
7. 性能优化与锁争用
-
避免长事务:事务内只包含必要的数据库操作,避免外部接口或延时任务的调用。
-
批量处理:对于需要更新大量数据的操作,尽量使用批量处理,减少事务锁的持有时间。
-
索引优化:确保数据库表有适当的索引,以加速查询和数据操作。
8. 审查与监控
8.1 代码审查标准
-
确保每个存储过程的事务控制符合规范。
-
确保异常处理机制完备,并且日志记录充分。
-
确保存储过程有清晰的返回状态码,便于系统监控。
8.2 监控与日志
-
监控长时间运行的事务,避免事务阻塞。
-
配置合适的告警机制,及时捕获死锁或其他异常。
总结:
-
通过分层设计,每个存储过程的责任清晰,主流程不控制事务,子事务控制事务,功能性操作不涉及事务管理。
-
存储过程命名规则统一,便于后续维护和扩展。
-
错误处理和日志记录是保证系统稳定性的关键,每个存储过程都应有明确的异常处理和日志记录机制。