Sql server 存储过程的优化

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

我不是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) 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
。这将在事务期间锁定表,以防止并发更新。目前来看,两个并发事务可能会使某人的余额为负(非常小)。我不认为将事务隔离级别序列化将完全消除这种可能性,但它将减少我使用设置事务隔离级别SERALIZABLEUse
MERGE
not
EXISTS
更新事务隔离级别的机会,因为您让DB解决问题,而不会有小窗口出错的风险。除非万不得已,否则不要使用
SERALIZABLE
。它会阻止并发性和吞吐量。我需要通过asp.net按钮调用它,即使我没有任何事务。你应该!!在db connection对象上,创建一个新事务,然后调用您的过程并提交,所有这些都包含一个try/catch来回滚事务。因为当你这样做的时候,如果发生错误,没有人会被警告,因为你只是通过回滚你的事务来隐藏错误OK我会尝试那种方式我完全不同意。在我看来,事务应该在存储过程中处理,必要时只能在应用程序层中处理。为了进一步了解我为什么会这样想,下面的答案基本上总结了我的观点——通过这种方法,您可以得到一个应用程序,其中一些方法将管理事务,因为您在其中调用了多个SP,而另一些方法不会,因为它们只调用了一个过程。然后,有一天,您将需要在mono SP transactionless方法中添加一个新SP。因此,您将开始使用此方法实现事务。在代码中检查没有人忘记在正确的位置管理事务,这将是一件非常愉快的事情……使用
TOP 1
而不使用
ORDER BY
总是导致不确定的结果,因此我怀疑查询部分的结果。如果没有示例数据和预期结果,很难验证它是否正常使用唯一使用的位置是
EXISTS
(它在哪里,因此我在我的版本中删除了它),但即使它做了一些事情,如果结果不确定,它也不相关,只有结果才重要。“如果结果是不确定的,那就没有关系,重要的是有结果。“非常正确,我想这是关于咖啡时间的,因为我一开始没有注意到,
不存在(…)
…因为现在anny数据库中的现代SQL优化器应该看到
不存在(选择*)
不存在(选择1..)
应该在“布尔模式”下执行“意思是只是检查是否有结果。