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

 

 
 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。