Sql server 存储过程的优化
我不是SQL Server方面的专家, 我有一个table Main RBDbalance和另一个RBDTransaction:Sql server 存储过程的优化,sql-server,Sql Server,我不是SQL Server方面的专家, 我有一个table Main RBDbalance和另一个RBDTransaction: CREATE TABLE [hybarmoney].[MAINRBDBALANCE] ( [ID] [int] IDENTITY(1,1) NOT NULL, [USERID] [bigint] NULL, [RBD] [decimal](18, 8) NULL, [CurrentDollar] [decimal](18, 8) NUL
CREATE TABLE [hybarmoney].[MAINRBDBALANCE]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[USERID] [bigint] NULL,
[RBD] [decimal](18, 8) NULL,
[CurrentDollar] [decimal](18, 8) NULL,
[EquivalentRBD] [decimal](18, 8) NULL,
[UpdatedRBD] [decimal](18, 8) NULL,
[PreviousRBDBeforUpdate] [decimal](18, 8) NULL,
[UPDATERBDFROMONEYEAAR] [decimal](18, 8) NULL,
[RBDbeforeupdatefromoneyear] [decimal](18, 8) NULL,
[TOBEADDEDFROM15DAYS] [decimal](18, 8) NULL,
[RBDBEFOREUPDATFROM15DAYS] [decimal](18, 8) NULL
)
RBD交易将包含生成的OTP以及将从谁处转账和向谁转账的金额
CREATE TABLE RbdTransaction
(
ID int identity(1,1),
RBD Decimal (18,8),
OTP Nvarchar(100),
FromUserID bigint,
ToUserID bigint,
Active tinyint,
CreatedDateTime Date
)
对于RBD从一个表到另一个表的事务,我编写了如下所示的存储过程,存储过程将检查MainRBDBALANCE表中的FromUsersID帐户中是否有RBD,然后需要检查RbdTransaction表中是否有RBD,然后需要更新这两个表
CREATE PROCEDURE UpdateRBDTransactionMainRBDBalance (
@OTP NVARCHAR(100)
,@FromUserID BIGINT
,@ToUserID BIGINT
,@RBD DECIMAL(18, 8)
)
AS
BEGIN
IF EXISTS (
SELECT TOP 1 1
FROM RbdTransaction
WHERE OTP = @OTP
AND FromUserID = @FromUserID
AND ToUserID = @ToUserID
AND RBD = @RBD
)
BEGIN
IF EXISTS (
SELECT TOP 1 1
FROM hybarmoney.MAINRBDBALANCE
WHERE USERID = @FromUserID
AND RBD >= @RBD
)
BEGIN
BEGIN TRY
BEGIN TRANSACTION
UPDATE hybarmoney.MAINRBDBALANCE
SET RBD = RBD - @RBD
WHERE USERID = @FromUserID
UPDATE RbdTransaction
SET Active = 0
WHERE OTP = @OTP
AND FromUserID = @FromUserID
AND ToUserID = @ToUserID
AND RBD = @RBD
IF EXISTS (
SELECT TOP 1 1
FROM hybarmoney.MAINRBDBALANCE
WHERE USERID = @ToUserID
)
BEGIN
UPDATE hybarmoney.MAINRBDBALANCE
SET RBD = RBD - @RBD
WHERE USERID = @ToUserID
END
ELSE
BEGIN
INSERT INTO hybarmoney.MAINRBDBALANCE (
RBD
,USERID
)
VALUES (
@RBD
,@ToUserID
)
END
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
END CATCH
END
END
END
我需要使用事务,因为我需要同时更新所有表。就我而言,这是在sql server中处理事务的正确方法。事实上,我并不热衷于在存储过程中处理事务,因为如果从管理事务的应用程序调用(这是一种很好的做法),您将遇到这种“双重管理”带来的问题
因此,我将从过程中删除TRY/CATCH和事务管理,然后像这样调用它:
开始尝试
开始交易;
UpdaterBDTransactionMainrBBalance(…);
提交事务;
结束尝试
开始捕捉
如果@TRANCOUNT=1
回滚事务
端接
您可以使用交易,但我倾向于改变您在此处检查余额的方式:
IF EXISTS (
SELECT TOP 1 1
FROM hybarmoney.MAINRBDBALANCE
WHERE USERID = @FromUserID
AND RBD >= @RBD
)
BEGIN
BEGIN TRY
BEGIN TRANSACTION
UPDATE hybarmoney.MAINRBDBALANCE
SET RBD = RBD - @RBD
WHERE USERID = @FromUserID
这为平衡在执行检查和更新之间的变化留下了一个很小的机会窗口。相反,我会在更新的同时进行检查:
UPDATE hybarmoney.MAINRBDBALANCE
SET RBD = RBD - @RBD
WHERE USERID = @FromUserID
AND RBD >= @RBD;
然后,您可以使用@@ROWCOUNT
检查此操作是否更新了任何行,如果更新了,则说明用户有可用资金,如果@@ROWCOUNT
返回0,则相当于未通过存在的检查
您还可以删除收件人帐户的EXISTS
检查,并将其替换为MERGE
:
MERGE hybarmoney.MAINRBDBALANCE AS t WITH (UPDLOCK, SERIALIZABLE)
USING (VALUES (@ToUserID)) AS s (UserID)
ON t.userID = s.UserID
WHEN MATCHED THEN
UPDATE SET RBD = RBD + @RBD
WHEN NOT MATCHED THEN
INSERT (UserID, USERID) VALUES (s.UserID, @RBD);
这再次消除了竞争条件导致重复用户的可能性。此处解释了表锁防止竞争条件的必要性-
因此,最终的程序将类似于:
CREATE PROCEDURE UpdateRBDTransactionMainRBDBalance (
@OTP NVARCHAR(100)
,@FromUserID BIGINT
,@ToUserID BIGINT
,@RBD DECIMAL(18, 8)
)
AS
BEGIN
IF EXISTS (
SELECT 1
FROM RbdTransaction
WHERE OTP = @OTP
AND FromUserID = @FromUserID
AND ToUserID = @ToUserID
AND RBD = @RBD
)
BEGIN
BEGIN TRY
BEGIN TRANSACTION
UPDATE hybarmoney.MAINRBDBALANCE
SET RBD = RBD - @RBD
WHERE USERID = @FromUserID
AND RBD >= @RBD;
IF @@ROWCOUNT > 0
BEGIN
UPDATE RbdTransaction
SET Active = 0
WHERE OTP = @OTP
AND FromUserID = @FromUserID
AND ToUserID = @ToUserID
AND RBD = @RBD;
MERGE hybarmoney.MAINRBDBALANCE AS t WITH (UPDLOCK, SERIALIZABLE)
USING (VALUES (@ToUserID)) AS s (UserID)
ON t.userID = s.UserID
WHEN MATCHED THEN
UPDATE SET RBD = RBD + @RBD
WHEN NOT MATCHED THEN
INSERT (UserID, USERID) VALUES (s.UserID, @RBD);
END
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
END CATCH
END
END
这取决于“你的目的”是什么。事务意味着如果事务中的语句失败,则之前的所有语句都将回滚。这就是你想要的吗?是的,这就是我想要的。我倾向于在该过程的开头添加SET TRANSACTION ISOLATION LEVEL SERALIZABLE
。这将在事务期间锁定表,以防止并发更新。目前来看,两个并发事务可能会使某人的余额为负(非常小)。我不认为将事务隔离级别序列化将完全消除这种可能性,但它将减少我使用设置事务隔离级别SERALIZABLEUseMERGE
notEXISTS
更新事务隔离级别的机会,因为您让DB解决问题,而不会有小窗口出错的风险。除非万不得已,否则不要使用SERALIZABLE
。它会阻止并发性和吞吐量。我需要通过asp.net按钮调用它,即使我没有任何事务。你应该!!在db connection对象上,创建一个新事务,然后调用您的过程并提交,所有这些都包含一个try/catch来回滚事务。因为当你这样做的时候,如果发生错误,没有人会被警告,因为你只是通过回滚你的事务来隐藏错误OK我会尝试那种方式我完全不同意。在我看来,事务应该在存储过程中处理,必要时只能在应用程序层中处理。为了进一步了解我为什么会这样想,下面的答案基本上总结了我的观点——通过这种方法,您可以得到一个应用程序,其中一些方法将管理事务,因为您在其中调用了多个SP,而另一些方法不会,因为它们只调用了一个过程。然后,有一天,您将需要在mono SP transactionless方法中添加一个新SP。因此,您将开始使用此方法实现事务。在代码中检查没有人忘记在正确的位置管理事务,这将是一件非常愉快的事情……使用TOP 1
而不使用ORDER BY
总是导致不确定的结果,因此我怀疑查询部分的结果。如果没有示例数据和预期结果,很难验证它是否正常使用唯一使用的位置是EXISTS
(它在哪里,因此我在我的版本中删除了它),但即使它做了一些事情,如果结果不确定,它也不相关,只有结果才重要。“如果结果是不确定的,那就没有关系,重要的是有结果。“非常正确,我想这是关于咖啡时间的,因为我一开始没有注意到,不存在(…)
…因为现在anny数据库中的现代SQL优化器应该看到不存在(选择*)
或不存在(选择1..)
应该在“布尔模式”下执行“意思是只是检查是否有结果。