zoukankan      html  css  js  c++  java
  • SQL Server:错误处理及事务控制

    目录:

    解读错误信息

    RAISERROR

    THROW

    实例

        使用 @@ERROR

        使用 XACT_ABORT

        使用TRY/CATCH

    现实中的事务语句

        删除

        更新

        银行取钱

    解读错误信息

    Msg 547, Level 16, State 0, Line 11
    The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Products_Categories".
    The conflict occurred in database "TSQL2012", table "Production.Categories", column 'categoryid'.

    Error number 

      ● SQL  Server 错误信息的编号从1~49999

      ● 自定义错误信息从50001开始

      ● 错误编号50000是为没有错误编号的自定义信息准备的。

    Severity  level

    SQL Server 一共26个严重级别  0~25。

      ● 严重级别>= 16的会记录SQL Server日志和Windows 应用程序日志

      ● 严重级别19~25 只能由 sysadmin觉得的成员处理

      ● 严重级别20~25被认为是致命错误。 会中断终端连接并回滚所有打开的事务。

      ● 严重级别0~10只是提示信息。

    State  int 类型,最大值127, MS internal purposes

    Error message  支持255个Unicode 字符

      ●  SQL  Server 错误信息都在  sys.messages里面

      ●  可以用sp_addmessage 添加自定义错误信息

    RAISERROR(不会中断事务)

    简单的传递信息可以使用级别0~9 。

    如果你有sysadmin的角色,可以使用WITH LOG选项并设置一个严重级别>20的错误。error 发生的时候SQL Server会中断连接。

    使用NOWAIT选项可以直接发送信息,而不用等大赛buffer

    RAISERROR ('Error in usp_InsertCategories stored procedure', 16, 0);
    
    -- Formatting the RAISERROR string
    RAISERROR ('Error in % stored procedure', 16, 0, N'usp_InsertCategories');
    
    -- In addition, you can use a variable: 
    GO
    DECLARE @message AS NVARCHAR(1000) = N'Error in % stored procedure';
    RAISERROR (@message, 16, 0, N'usp_InsertCategories');
    
    -- And you can add the formatting outside RAISERROR using the FORMATMESSAGE function:
    GO
    DECLARE @message AS NVARCHAR(1000) = N'Error in % stored procedure';
    SELECT @message = FORMATMESSAGE (@message, N'usp_InsertCategories');
    RAISERROR (@message, 16, 0);

    THROW (会中断事务)

    -- You can issue a simple THROW as follows:
    THROW 50000, 'Error in usp_InsertCategories stored procedure', 0;
    
    -- Because THROW does not allow formatting of the message parameter, you can use FORMATMESSAGE()
    GO
    DECLARE @message AS NVARCHAR(1000) = N'Error in % stored procedure';
    SELECT @message = FORMATMESSAGE (@message, N'usp_InsertCategories');
    THROW 50000, @message, 0;
    -- RAISERROR does not normally terminate a batch:
    RAISERROR ('Hi there', 16, 0);
    PRINT 'RAISERROR error'; -- Prints
    GO
    
    -- However, THROW does terminate the batch:
    THROW 50000, 'Hi there', 0;
    PRINT 'THROW error'; -- Does not print
    GO

    实例

    使用 @@ERROR

    DECLARE @errnum AS int;
    BEGIN TRAN;
    SET IDENTITY_INSERT Production.Products ON;
    INSERT INTO Production.Products(productid, productname, supplierid, categoryid, unitprice, discontinued)
        VALUES(1, N'Test1: Ok categoryid', 1, 1, 18.00, 0);
    SET @errnum = @@ERROR; 
    IF @errnum <> 0 -- Handle the error
        BEGIN 
            PRINT 'Insert into Production.Products failed with error ' + CAST(@errnum AS VARCHAR);
        END
    DECLARE @errnum AS int;
    BEGIN TRAN;
        SET IDENTITY_INSERT Production.Products ON;
        -- Insert #1 will fail because of duplicate primary key
        INSERT INTO Production.Products(productid, productname, supplierid, categoryid,     unitprice, discontinued)
            VALUES(1, N'Test1: Ok categoryid', 1, 1, 18.00, 0);
        SET @errnum = @@ERROR;
        IF @errnum <> 0
            BEGIN 
                IF @@TRANCOUNT > 0 ROLLBACK TRAN;
                PRINT 'Insert #1 into Production.Products failed with error ' + CAST(@errnum AS VARCHAR);
            END; 
        -- Insert #2 will succeed
        INSERT INTO Production.Products(productid, productname, supplierid, categoryid,     unitprice, discontinued)
            VALUES(101, N'Test2: Bad categoryid', 1, 1, 18.00, 0);
        SET @errnum = @@ERROR;
        IF @errnum <> 0
            BEGIN 
                IF @@TRANCOUNT > 0 ROLLBACK TRAN;
                PRINT 'Insert #2 into Production.Products failed with error ' + CAST(@errnum AS VARCHAR);
            END; 
        SET IDENTITY_INSERT Production.Products OFF;
        IF @@TRANCOUNT > 0 COMMIT TRAN;
    -- Remove the inserted row
    DELETE FROM Production.Products WHERE productid = 101;
    PRINT 'Deleted ' + CAST(@@ROWCOUNT AS VARCHAR) + ' rows';

    使用 XACT_ABORT

    使用XACT_ABORT,语句中发生错误,整段语句都会中止。

    SET XACT_ABORT ON;
    PRINT 'Before error';
    SET IDENTITY_INSERT Production.Products ON;
    INSERT INTO Production.Products(productid, productname, supplierid, categoryid, unitprice, discontinued)
        VALUES(1, N'Test1: Ok categoryid', 1, 1, 18.00, 0);
    SET IDENTITY_INSERT Production.Products OFF;
    PRINT 'After error';
    GO
    PRINT 'New batch';
    SET XACT_ABORT OFF;
    -- Using THROW with XACT_ABORT. 
    USE TSQL2012;
    GO
    SET XACT_ABORT ON;
    PRINT 'Before error';
    THROW 50000, 'Error in usp_InsertCategories stored procedure', 0;
    PRINT 'After error';
    GO
    PRINT 'New batch';
    SET XACT_ABORT OFF;

    @@ERROR第二个例子中使用XACT_ABORT以后,第二条语句这回就无效了。

    DECLARE @errnum AS int;
    SET XACT_ABORT ON; 
    BEGIN TRAN;
        SET IDENTITY_INSERT Production.Products ON;
        -- Insert #1 will fail because of duplicate primary key
        INSERT INTO Production.Products(productid, productname, supplierid, categoryid,     unitprice, discontinued)
            VALUES(1, N'Test1: Ok categoryid', 1, 1, 18.00, 0);
        SET @errnum = @@ERROR;
        IF @errnum <> 0
            BEGIN 
                IF @@TRANCOUNT > 0 ROLLBACK TRAN;
                PRINT 'Error in first INSERT';
            END; 
        -- Insert #2 no longer succeeds
        INSERT INTO Production.Products(productid, productname, supplierid, categoryid,     unitprice, discontinued)
            VALUES(101, N'Test2: Bad categoryid', 1, 1, 18.00, 0);
        SET @errnum = @@ERROR;
        IF @errnum <> 0
            BEGIN 
                -- Take actions based on the error
                IF @@TRANCOUNT > 0 ROLLBACK TRAN;
                PRINT 'Error in second INSERT';
            END; 
        SET IDENTITY_INSERT Production.Products OFF;
        IF @@TRANCOUNT > 0 COMMIT TRAN;
    GO
     
    DELETE FROM Production.Products WHERE productid = 101;
    PRINT 'Deleted ' + CAST(@@ROWCOUNT AS VARCHAR) + ' rows'; 
    SET XACT_ABORT OFF;
    GO
    SELECT XACT_STATE(), @@TRANCOUNT;

    使用TRY/CATCH

    格式

    --Transactions extend batches
    BEGIN TRY
     BEGIN TRANSACTION 
      INSERT INTO Sales.SalesOrderHeader... --Succeeds
      INSERT INTO Sales.SalesOrderDetail... --Fails
     COMMIT TRANSACTION -- If no errors, transaction completes
    END TRY
    BEGIN CATCH
     --Inserted rows still exist in Sales.SalesOrderHeader SELECT ERROR_NUMBER()
     ROLLBACK TRANSACTION --Any transaction work undone
    END CATCH;
    BEGIN TRY
    BEGIN TRAN;
        SET IDENTITY_INSERT Production.Products ON;
        INSERT INTO Production.Products(productid, productname, supplierid, categoryid, unitprice, discontinued)
            VALUES(1, N'Test1: Ok categoryid', 1, 1, 18.00, 0);
        INSERT INTO Production.Products(productid, productname, supplierid, categoryid, unitprice, discontinued)
            VALUES(101, N'Test2: Bad categoryid', 1, 10, 18.00, 0);
        SET IDENTITY_INSERT Production.Products OFF;
    COMMIT TRAN;
    END TRY
    BEGIN CATCH
        IF ERROR_NUMBER() = 2627 -- Duplicate key violation
            BEGIN
                PRINT 'Primary Key violation';
            END
        ELSE IF ERROR_NUMBER() = 547 -- Constraint violations
            BEGIN
                PRINT 'Constraint violation';
            END
        ELSE
            BEGIN
                PRINT 'Unhandled error';
            END;
        IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
    END CATCH;
    -- revise the CATCH block using variables to capture error information and re-raise the error using RAISERROR. 
    USE TSQL2012;
    GO
    SET NOCOUNT ON;
    DECLARE @error_number AS INT, @error_message AS NVARCHAR(1000), @error_severity AS INT;
    BEGIN TRY
    BEGIN TRAN;
        SET IDENTITY_INSERT Production.Products ON;
        INSERT INTO Production.Products(productid, productname, supplierid, categoryid,         unitprice, discontinued)
            VALUES(1, N'Test1: Ok categoryid', 1, 1, 18.00, 0);
        INSERT INTO Production.Products(productid, productname, supplierid, categoryid,         unitprice, discontinued)
            VALUES(101, N'Test2: Bad categoryid', 1, 10, 18.00, 0);
        SET IDENTITY_INSERT Production.Products OFF;
        COMMIT TRAN;
    END TRY
    BEGIN CATCH
        SELECT XACT_STATE() as 'XACT_STATE', @@TRANCOUNT as '@@TRANCOUNT';
        SELECT @error_number = ERROR_NUMBER(), @error_message = ERROR_MESSAGE(), @error_severity = ERROR_SEVERITY();
        RAISERROR (@error_message, @error_severity, 1);
        IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
    END CATCH;
    -- use a THROW statement without parameters re-raise (re-throw) the original error message and send it back to the client. 
    USE TSQL2012;
    GO
    BEGIN TRY
    BEGIN TRAN;
        SET IDENTITY_INSERT Production.Products ON;
        INSERT INTO Production.Products(productid, productname, supplierid, categoryid,         unitprice, discontinued)
            VALUES(1, N'Test1: Ok categoryid', 1, 1, 18.00, 0);
        INSERT INTO Production.Products(productid, productname, supplierid, categoryid,         unitprice, discontinued)
            VALUES(101, N'Test2: Bad categoryid', 1, 10, 18.00, 0);
        SET IDENTITY_INSERT Production.Products OFF;
    COMMIT TRAN;
    END TRY
    BEGIN CATCH
        SELECT XACT_STATE() as 'XACT_STATE', @@TRANCOUNT as '@@TRANCOUNT';
        IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
        THROW;
    END CATCH;
    GO
    SELECT XACT_STATE() as 'XACT_STATE', @@TRANCOUNT as '@@TRANCOUNT';

     

    现实中的事务语句

    删除

    --删除
    CREATE PROCEDURE [dbo].[Students_Delete](@ID int)
    WITH EXECUTE AS CALLER
    AS
    BEGIN
        --Check to make sure the ID does exist
        --If not does, return error
        DECLARE @existing AS int = 0
        SELECT @existing = count(ID)  
        FROM Students
        WHERE ID = @ID
        
        IF @existing <> 1
        BEGIN
            RAISERROR ('ID does not exist', 1, 1)
            RETURN 0
        END
            --Attempt Delete
            DELETE FROM [dbo].[Students]
            WHERE ID = @ID
         
            --check to see if update occured 
            --and return status
            IF @@ROWCOUNT = 1
                BEGIN
                    INSERT INTO StudentDeleteLog 
                    VALUES (suser_sname(), @ID, getdate())
                    RETURN 1
                END
                
            ELSE 
                RETURN 0
    END
    GO

    更新

    CREATE PROCEDURE [dbo].[Students_Update]
    (    @ID int,
            @LASTNAME varchar(50),
            @FIRSTNAME varchar(50),
            @STATE varchar(50),
            @PHONE varchar(50),
            @EMAIL varchar(50),
        @GRADYEAR int,
           @GPA decimal(20,10),
        @PROGRAM varchar(50),
        @NEWSLETTER bit
    )
    AS
    BEGIN
        --Check to make sure the ID does exist
        --If not does, return error
        DECLARE @existing AS int = 0
        SELECT @existing = count(ID)  
        FROM Students
        WHERE ID = @ID
        
        IF @existing <> 1
        BEGIN
            RAISERROR ('ID does not exist', 1, 1)
            RETURN 0
        END
        --Can not subscribe to newsletter if email is null
        IF (@email IS NULL)
            SET @NEWSLETTER = 0
     
        --Attempt Update
    UPDATE [dbo].[Students]
       SET [LASTNAME] = @LASTNAME 
          ,[FIRSTNAME] = @FIRSTNAME 
          ,[STATE] = @STATE 
          ,[PHONE] = @PHONE 
          ,[EMAIL] = @EMAIL 
          ,[GRADYEAR] = @GRADYEAR 
          ,[GPA] = @GPA 
          ,[PROGRAM] = @PROGRAM 
          ,[NEWSLETTER] = @NEWSLETTER 
     WHERE ID = @ID
         
               --check to see if update occured 
               --and return status
               IF @@ROWCOUNT = 1
                    RETURN 1
               ELSE 
                    RETURN 0
    END
    GO

    银行取钱

    BEGIN TRAN;
        IF NOT EXISTS (
            SELECT * FROM Accounts WITH(UPDLOCK)  --只有当前的事务可以查看
            WHERE AccountID = 47387438 AND Balance >= 400
        )
        BEGIN
            ROOLBACK TRAN;
            THROW 50000,'Tobias is too poor',1;
        END
        UPDATE Accounts SET
            Balance -=400
        WHERE AccountID = 47387438;
    COMMIT TRAN;
    
    --银行取钱高效版本
    BEGIN TRAN;
        UPDATE Accounts SET
            Balance -= 400
        WHERE AccountID = 47387438 AND Balance >= 400
        IF(@@ROWCOUNT <> 1)
        BEGIN
            ROLLBACK TRAN;
            THROW 50000,'Tobias is too poor ',1;
        END
    COMMIT TRAN;

    参考文档

    Database Engine Error Severities

    https://msdn.microsoft.com/en-us/library/ms164086.aspx

    SET XACT_ABORT (Transact-SQL)

    https://msdn.microsoft.com/zh-tw/library/ms188792.aspx

     
     
  • 相关阅读:
    ACM ICPC 2008–2009 NEERC MSC A, B, C, G, L
    POJ 1088 滑雪 DP
    UVA 11584 最短回文串划分 DP
    POJ 2531 Network Saboteur DFS+剪枝
    UVa 10739 String to Palindrome 字符串dp
    UVa 11151 Longest Palindrome 字符串dp
    UVa 10154 Weights and Measures dp 降维
    UVa 10271 Chopsticks dp
    UVa 10617 Again Palindrome 字符串dp
    UVa 10651 Pebble Solitaire 状态压缩 dp
  • 原文地址:https://www.cnblogs.com/haseo/p/4380340.html
Copyright © 2011-2022 走看看