如何找出查询的哪一部分导致错误?

时间:2019-11-13 06:26:46

标签: sql sql-server

运行此查询后,出现一个自定义错误:

  

借记帐户余额不能少于0。somemail@7dmail.com/123/xxx/123456

以及两个常规错误:

  

EXECUTE之后的事务计数表明BEGIN和COMMIT语句的数量不匹配。上一个计数= 2,当前计数= 0。   ROLLBACK TRANSACTION请求没有相应的BEGIN TRANSACTION。

我认为,发生事务错误是因为在事务提交之前某些东西引发异常。 自定义错误来自下面编写的另一个查询(AddJournalEntry)。我看不到这两个查询之间的连接。

查询:

-- =============================================
-- Author:      <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
CREATE  PROCEDURE [dbo].[RevokeOrder]
    @OrderId int  = null
    --,@EntryId int OUTPUT

AS

    declare @OrderTypeId nvarchar(30)
    declare @MasterEntryId int
    declare @NewEntryId int
    declare @CustomerGuid uniqueidentifier
    declare @Debit int
    declare @Credit int
    declare @Explanation nvarchar(100)

    declare @Amount decimal(18, 8)
    declare @AmountFilled decimal(18, 8)

    declare @Total decimal(18, 8)
    declare @TotalLeft decimal(18, 8)

    declare @AmountLeft decimal(18, 8)
    declare @AssetId nvarchar(30)
    declare @QuoteAssetId nvarchar(30)
    declare @QuotePrice decimal(18, 8)

    declare @AssetReserveAccountId int
    declare @AssetAccountId int
    declare @EntryAmount decimal(18, 8)

BEGIN;
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET XACT_ABORT ON;
    SET NOCOUNT ON;

    print ''
    print '++++++++++++++++++++++++++++++++++++++++++++++++'
    print 'Start RevokeOrder procedure' 
    print '++++++++++++++++++++++++++++++++++++++++++++++++'
    print ''

    begin tran

        select 
            @OrderTypeId = OrderTypeId, 
            @CustomerGuid = CustomerGuid,
            @Amount = Amount,
            @AmountFilled = AmountFilled,
            @AssetId = AssetId,
            @QuoteAssetId = QuoteAssetId,
            @QuotePrice = QuotePrice,
            @Total = Total,
            @TotalLeft = TotalLeft
        from dbo.[vOrder] WITH (HOLDLOCK, ROWLOCK)
        where OrderId = @OrderId

        if @@ERROR <> 0 or @@ROWCOUNT = 0
        begin
            rollback
            raiserror ('Order not found', 16, 1)
            return 3
        end


        set @AmountLeft = @Amount - ISNULL(@AmountFilled, 0)


        if (@OrderTypeId = 'buy')
        begin

            --declare @Released decimal(18, 8)
            --select @Released = coalesce(SUM(Total), 0)
            --from dbo.[vOrder]
            --where OrderId in (select FillerId from dbo.Filler where OrderId = @OrderId)
            --or OrderId in (select OrderId from dbo.Filler where FillerId = @OrderId)

            declare @Released decimal(18, 8)
            select @Released = Amount 
            from dbo.Trade t join dbo.JournalEntry j on t.EntryId = j.EntryId
            where SourceOrderId = @OrderId
            set @Released = ISNULL(@Released, 0)

                print 'Order Type = ' + @OrderTypeId
                print '@InitialAmount = ' + isnull(cast (@Amount as nvarchar), 'NULL') + ' ' + @AssetId
                print '@AmountFilled = ' + isnull(cast (@AmountFilled as nvarchar), 'NULL') + ' ' + @AssetId
                print '@AmountLeft = ' + isnull(cast (@AmountLeft as nvarchar), 'NULL')
                print ''
                print '@InitialBlocked = ' + isnull(cast (@Total as nvarchar), 'NULL') + ' ' + @QuoteAssetId
                print '@Released = ' + cast (@Released as nvarchar) + ' ' + @QuoteAssetId
                print '@CurrentBlocked = ' + isnull(cast (@TotalLeft as nvarchar), 'NULL') + ' ' + @QuoteAssetId
                print ''

            set @Explanation = N'Revoke order process. Reverse blocked quote amount' --+ 'reverse' --+ cast(@EntryId as nvarchar)
            DECLARE @RC int
            declare @Date datetimeoffset
            set @Date = sysdatetimeoffset()

            set @AssetReserveAccountId = (select AccountId from dbo.Account where CustomerGuid = @CustomerGuid and MasterAccountNo = 99931 and AssetId = @QuoteAssetId)
            set @AssetAccountId = (select AccountId from dbo.Account where CustomerGuid = @CustomerGuid and MasterAccountNo = 9993 and AssetId = @QuoteAssetId)


            set @EntryAmount = @Amount * @QuotePrice - @Released
            print 'Order total = ' + cast (@Amount * @QuotePrice as nvarchar)
            print 'Money spent to buy asset = ' + cast (@Released as nvarchar)
            print 'Money to refund to buyer = ' + cast (@EntryAmount as nvarchar)
            print '@Amount = ' + cast (@Amount as nvarchar)
            print '@QuotePrice = ' + cast (@QuotePrice as nvarchar)


            --rollback
            --return 111

            EXECUTE @RC = [dbo].[AddJournalEntry] 
               @Date
              ,@AssetReserveAccountId
              ,@AssetAccountId
              ,@EntryAmount
              ,@QuoteAssetId
              ,@Explanation
              ,'revoke'
              ,@OrderId
              ,@MasterEntryId
              ,@NewEntryId OUTPUT
            if @@ERROR <> 0 or @RC <> 0
            begin
                rollback
                raiserror ('Revoke order process. Can not add reverse blocked quote amount journal entry', 16, 1)
                return 4
            end 
        end

        if (@OrderTypeId = 'sell')
        begin

            print 'sell order'

            set @Explanation = N'Revoke order process. Reverse blocked amount journal entry' --+ 'რევერსი' --+ cast(@EntryId as nvarchar)
            set @Date = sysdatetimeoffset()

            set @AssetReserveAccountId = (select AccountId from dbo.Account where CustomerGuid = @CustomerGuid and MasterAccountNo = 99931 and AssetId = @AssetId)
            set @AssetAccountId = (select AccountId from dbo.Account where CustomerGuid = @CustomerGuid and MasterAccountNo = 9993 and AssetId = @AssetId)

            set @EntryAmount = @AmountLeft
            print '@EntryAmount = ' + isnull(cast (@EntryAmount as nvarchar), 'NULL')

            EXECUTE @RC = [dbo].[AddJournalEntry] 
               @Date
              ,@AssetReserveAccountId
              ,@AssetAccountId
              ,@EntryAmount
              ,@AssetId
              ,@Explanation
              ,'revoke'
              ,@OrderId
              ,@MasterEntryId
              ,@NewEntryId OUTPUT
            if @@ERROR <> 0 or @RC <> 0
            begin
                rollback
                raiserror ('Revoke order process. Can not add reverse blocked amount journal entry', 16, 1)
                return 5
            end

        end

        -- STEP 4

        update dbo.[Order]
        set OrderStatusId = 30
        where OrderId = @OrderId
        if @@ERROR <> 0 or @@ROWCOUNT = 0
        begin
            rollback
            raiserror ('Can not set order status to REVOKED', 16, 1)
            return 2
        end

    commit tran

    return 0
END
go

我得到的自定义错误是在名为的查询中定义的 AddJournalEntry

-- =============================================
-- Author:      <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
CREATE PROCEDURE [dbo].[AddJournalEntry]
    @Date datetimeoffset,
    @Debit int,
    @Credit int,
    @Amount decimal(18, 8),
    @AssetId nvarchar(50),
    @Explanation nvarchar(100),
    @EntryType nvarchar(50),
    @OrderId int  = null,
    @MasterEntryId int = null,
    @EntryId int OUTPUT

AS
declare @DebitBalance decimal(18, 8)
declare @DebitAccountAssetId nvarchar(10)
declare @CreditAccountAssetId nvarchar(10)
declare @CreditBalance decimal(18, 8)
declare @ToIncrease nvarchar(100)
declare @DebitAccountTitle nvarchar(500)
declare @CreditAccountTitle nvarchar(500)
declare @Error nvarchar(500)

BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET XACT_ABORT ON;
    SET NOCOUNT ON;

    print ''
    print '++++++++++++++++++++++++++++++++++++++++++++++++'
    print 'Start AddJournalEntry procedure' 
    print '++++++++++++++++++++++++++++++++++++++++++++++++'
    print ''


    begin tran
-- STEP 1

        print '@Debit = ' + isnull(cast(@Debit as nvarchar), 'NULL')
        print '@Credit = ' + isnull(cast(@Credit as nvarchar), 'NULL')
        print '@AssetId = ' + cast(@AssetId as nvarchar(50))
        print '@Amount = ' + cast(@Amount as nvarchar(50))

        update dbo.Account
        set Debit = Debit + @Amount, LastTransactionDate = SYSDATETIMEOFFSET()
        where AccountId = @Debit
        if @@ERROR <> 0 or @@ROWCOUNT = 0
        begin
            rollback
            raiserror ('Can not update debit account balance', 16, 1)
            return 2
        end

        update dbo.Account
        set Credit = Credit + @Amount, LastTransactionDate = SYSDATETIMEOFFSET()
        where AccountId = @Credit
        if @@ERROR <> 0 or @@ROWCOUNT = 0
        begin
            rollback
            raiserror ('Can not find or update credit account balance', 16, 1)
            return 3
        end

        select 
            @DebitBalance = Balance, 
            @ToIncrease = ToIncrease,
            @DebitAccountTitle = AccountFullTitle
        from dbo.vAccount 
        where AccountId = @Debit and AssetId = @AssetId

        if @@ERROR <> 0  or @@ROWCOUNT = 0
        begin
            rollback
            raiserror ('Can not find debit account', 16, 1)
            return 4
        end

        print 'New Debit Balance = ' + cast(@DebitBalance as nvarchar(50))

        if (@DebitBalance < 0 and @ToIncrease = 'debit') or (@DebitBalance > 0 and @ToIncrease = 'credit')
        begin
            rollback
            set @Error = 'Debit account balance can not be less than 0. ' + @DebitAccountTitle
            raiserror (@Error, 16, 1)
            return 5
        end
-- STEP 4
        select 
            @CreditBalance = Balance, 
            @ToIncrease = ToIncrease,
            @CreditAccountTitle = AccountFullTitle
        from dbo.vAccount 
        where AccountId = @Credit and AssetId = @AssetId
        if @@ERROR <> 0  or @@ROWCOUNT = 0
        begin
            rollback
            raiserror ('Can not find credit account', 16, 1)
            return 55
        end

        if (@CreditBalance > 0 and @ToIncrease = 'credit') or (@CreditBalance < 0 and @ToIncrease = 'debit')
        begin
            rollback
            set @Error = 'Credit account balance can not be less than 0. ' + @CreditAccountTitle
            raiserror ( @Error, 16, 1)
            return 56
        end
-- STEP 4
        insert dbo.JournalEntry
        select SYSDATETIMEOFFSET(), @Debit, @Credit, @Amount, @AssetId, @Explanation, @DebitBalance, @CreditBalance, @OrderId, @MasterEntryId, @EntryType, NEWID()
        if @@ERROR <> 0
        begin
            rollback
            raiserror ('Can not insert entry record', 16, 1)
            return 1
        end

    commit tran

    set @EntryId = SCOPE_IDENTITY()

    return 0
END
go

1 个答案:

答案 0 :(得分:1)

要执行多个ROLLBACK命令,而只执行一次。回滚会将事务计数从大于0的任何金额直接降低到0,因此如果执行3 BEGIN TRANSACTION,则@@TRANCOUNT为3,回滚会将其设置为0。问题是您在被调用的SP(嵌套的SP)内部执行回滚,并在SP返回之后再次执行回滚。

您可以在此示例中看到问题:

BEGIN TRANSACTION
SELECT @@TRANCOUNT -- 1
BEGIN TRANSACTION
SELECT @@TRANCOUNT -- 2
ROLLBACK
SELECT @@TRANCOUNT -- 0
ROLLBACK -- The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.

这是您的SP执行失败的路线:

CREATE  PROCEDURE [dbo].[RevokeOrder]
    @OrderId int  = null
AS

    begin tran -- Create a transaction here (TRANCOUNT = 1)

        if (...)
        begin

            EXECUTE @RC = [dbo].[AddJournalEntry] -- Executes a rollback inside

            if @@ERROR <> 0 or @RC <> 0
            begin
                rollback -- When the execution reaches this rollback, TRANCOUNT is 0 and the rollback fails
                raiserror ('Revoke order process. Can not add reverse blocked quote amount journal entry', 16, 1)
                return 4
            end 
        end
END

SP被称为:

CREATE PROCEDURE [dbo].[AddJournalEntry]
AS
BEGIN

    begin tran -- TRANCOUNT = 2

        if (@DebitBalance < 0 and @ToIncrease = 'debit') or (@DebitBalance > 0 and @ToIncrease = 'credit')
        begin
            rollback -- Undoes all changes from the start of the first BEGIN TRAN and sets TRANCOUNT to 0
            set @Error = 'Debit account balance can not be less than 0. ' + @DebitAccountTitle
            raiserror (@Error, 16, 1)
            return 5
        end

END

我建议使用TRY / CATCH块并在ROLLBACK上进行CATCH。如下所示:

CREATE  PROCEDURE [dbo].[RevokeOrder]
    @OrderId int  = null
AS

    BEGIN TRY

        begin tran

            if (...)
            begin

                EXECUTE @RC = [dbo].[AddJournalEntry]

                if @@ERROR <> 0 or @RC <> 0
                begin
                    raiserror ('Revoke order process. Can not add reverse blocked quote amount journal entry', 16, 1)
                    return 4
                end 
            end

        COMMIT

    END TRY

    BEGIN CATCH

        IF @@TRANCOUNT > 0 -- Might want to check XACT State also
            ROLLBACK

        -- Additional logging/fixing stuff

    END CATCH
END

有关SQL Server错误处理的详细说明,请选中this post