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

时间: 2025-01-18 04:05:48

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

SQL Server错误收集#3

错误#1 16:50 2014-5-20安装好数据库(08R2),启动数据库代理服务失败,当时也没在意.后来装上SQL12,再次启动数据库代理依旧失败.不能再得过且过,该找找具体原因了.查看SQLAGENT代理日志: 2014-05-20 16:51:33 - ? [100] Microsoft SQLServerAgent 版本 11.0.2100.60 (内部版本号 x86 unicode 零售): 进程 ID 3076 2014-05-20 16:51:33 - ? [495] SQL S

SQL Server自动化运维系列——监控磁盘剩余空间及SQL Server错误日志(Power Shell)

原文:SQL Server自动化运维系列--监控磁盘剩余空间及SQL Server错误日志(Power Shell) 需求描述 在我们的生产环境中,大部分情况下需要有自己的运维体制,包括自己健康状态的检测等.如果发生异常,需要提前预警的,通知形式一般为发邮件告知. 在所有的自检流程中最基础的一个就是磁盘剩余空间检测.作为一个高效的DBA不可能每天都要上生产机上查看磁盘剩余或者直到磁盘无剩余空间报错后才采取扩容措施. 当然,作为微软的服务器有着自己的监控软件:SCCM(System Center

SQL Server 2016 行级别权限控制

原文:SQL Server 2016 行级别权限控制 背景 假如我们有关键数据存储在一个表里面,比如人员表中包含员工.部门和薪水信息.只允许用户访问各自部门的信息,但是不能访问其他部门.一般我们都是在程序端实现这个功能,而在sqlserver2016以后也可以直接在数据库端实现这个功能. 解决 安全已经是一个数据方面的核心问题,每一代的MS数据库都有关于安全方面的新功能,那么在Sql Server 2016,也有很多这方面的升级,比如'Row Level Security', 'Always E

sql server 错误日志errorlog

一 .概述 SQL Server 将某些系统事件和用户定义事件记录到 SQL Server 错误日志和 Microsoft Windows 应用程序日志中. 这两种日志都会自动给所有记录事件加上时间戳. 使用 SQL Server 错误日志中的信息可以解决SQL Server的相关问题. 查看 SQL Server 错误日志可以确保进程(例如,备份和还原操作.批处理命令或其他脚本和进程)成功完成. 此功能可用于帮助检测任何当前或潜在的问题领域,包括自动恢复消息(尤其是在 SQL Server 实

SQL Server错误收集#6

错误#1 22:26 2014-7-30 重置连接数对实例->属性->连接->最大并发连接数不是特别理解,昨天下午心血来潮,把连接数改成1,不断开启新的查询窗口,并没有按预想的出错(当时没有重启数据库服务).今天早上打开电脑,打开对象资源管理器,连接到服务器时报错. 查看ERRORLOG,错误信息很明显,超过最大并发连接数. 2014-07-30 09:35:37.12 登录 错误: 17809,严重性: 20,状态: 3. 2014-07-30 09:35:37.12 登录 Could

SQL SERVER错误:已超过了锁请求超时时段。 (Microsoft SQL Server,错误: 1222)

在SSMS(Microsoft SQL Server Management Studio)里面,查看数据库对应的表的时候,会遇到"Lock Request time out period exceeded.(Microsoft SQL Server, 错误1222)",对应的中文错误提示为"已超过了锁请求超时时段. (Microsoft SQL Server,错误: 1222)",如下截图所示,不管是用一般权限的账号还是具有sysadmin角色的登录名都是如此. 这

sql server中的锁 事务锁 更新锁 保持锁 共享锁 你知道吗?

锁定数据库的一个表 SELECT * FROM table WITH (HOLDLOCK) 注意: 锁定数据库的一个表的区别 SELECT * FROM table WITH (HOLDLOCK) 其他事务可以读取表,但不能更新删除 SELECT * FROM table WITH (TABLOCKX) 其他事务不能读取表,更新和删除 SELECT 语句中"加锁选项"的功能说明 SQL Server提供了强大而完备的锁机制来帮助实现数据库系统的并发性和高性能.用户既能使用SQL Ser

sql连接错误(Microsoft SQL Server,错误:2)

昨天用SQL语句建表的时候写了一段代码,对于代码的逻辑和内容我不太肯定对不对,反正是毫不犹豫的让它执行了,过程中出现好几个错误,当时没有太在意,想着大不了出错了再重写一个,结果--玩坏了,从昨天到现在十几个小时,SQL Server毫无商量的给我罢工了!于是乎,漫长的"寻错"之路开始了. 先看下出错信息: 1.通过以往经验我先打开了SQL Server配置工具-->配置管理器,检查里边的协议是否开启,就在这时我又犯了一个错误.因为不知道那些协议到底是什么意思,索性干脆都启用了,结