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)
SQL Server:错误处理及事务控制的更多相关文章
- SQL Server 2014新特性——事务持久性控制
控制事务持久性 SQL Server 2014之后事务分为2种:完全持久, 默认或延迟的持久. 完全持久,当事务被提交之后,会把事务日志写入到磁盘,完成后返回给客户端. 延迟持久,事务提交是异步的,在 ...
- SQL SERVER错误:已超过了锁请求超时时段。 (Microsoft SQL Server,错误: 1222)
在SSMS(Microsoft SQL Server Management Studio)里面,查看数据库对应的表的时候,会遇到"Lock Request time out period e ...
- SQL Server自动化运维系列——监控磁盘剩余空间及SQL Server错误日志(Power Shell)
需求描述 在我们的生产环境中,大部分情况下需要有自己的运维体制,包括自己健康状态的检测等.如果发生异常,需要提前预警的,通知形式一般为发邮件告知. 在所有的自检流程中最基础的一个就是磁盘剩余空间检测. ...
- SQL Server 错误日志收缩(ERRORLOG)
一.基础知识 默认情况下,错误日志位于 : C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\LOG\ERRORLOG 和ERRORLOG.n 文 ...
- SQL Server自动化运维系列 - 监控磁盘剩余空间及SQL Server错误日志(Power Shell)
需求描述 在我们的生产环境中,大部分情况下需要有自己的运维体制,包括自己健康状态的检测等.如果发生异常,需要提前预警的,通知形式一般为发邮件告知. 在所有的自检流程中最基础的一个就是磁盘剩余空间检测. ...
- sql server 错误日志errorlog
一 .概述 SQL Server 将某些系统事件和用户定义事件记录到 SQL Server 错误日志和 Microsoft Windows 应用程序日志中. 这两种日志都会自动给所有记录事件加上时间戳 ...
- SQL Server 存储过程,带事务的存储过程(创建存储过程,删除存储过程,修改存储过
存储过程 创建存储过程 use pubs --pubs为数据库 go create procedure MyPRO --procedure为创建存储过程关键字,也可以简写proc,MyPRO为存储过程 ...
- SQL Server 自动化运维系列 - 监控磁盘剩余空间及SQL Server错误日志(Power Shell)
需求描述 在我们的生产环境中,大部分情况下需要有自己的运维体制,包括自己健康状态的检测等.如果发生异常,需要提前预警的,通知形式一般为发邮件告知. 在所有的自检流程中最基础的一个就是磁盘剩余空间检测. ...
- sql server 2008启动时:已成功与服务器建立连接,但是在登录过程中发生错误。(provider:命名管道提供程序,error:0-管道的另一端上无任何进程。)(Microsoft SQL Server,错误:233) 然后再连接:错误:18456
问题:sql server 2008启动时:已成功与服务器建立连接,但是在登录过程中发生错误.(provider:命名管道提供程序,error:0-管道的另一端上无任何进程.)(Microsoft S ...
随机推荐
- 解决一个Android Studio gradle的小问题
自从Android Studio有了gradle之后,就经常有问题,最近在Ubuntu上用Android Studio的时候就遇到一个问题,每次项目目录更改了,Import项目,打开项目,还是新建项目 ...
- PhpStorm 10.0 激活方式
随着 JetBrains 新版本的发布,注册机已然不行了.然而,道高一尺,魔高一丈.IntelliJ IDEA开源社区 提供了如下通用激活方法:注册时选择License server填写http:// ...
- ThinkPHP中的CURD操作
<?php //查询多条记录,返回二维数组 $result = M("admin")->select(); $result = M("admin") ...
- jQuery.validationEngine前端验证
引入相关文件: <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.js" type ...
- 使用VisualVM分析tomcat运行状况(1)
VisualVM是一款java程序性能分析与调优工具,而且还是jdk中自带的工具之一. tomcat也是一个java程序,自然也可以用它来进行监控.不过这里还是会有些问题,tomcat有两种常用的期待 ...
- 用1个 2个3个 5个div实现 十字架
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 利用 Android Studio 和 Gradle 打包多版本APK
在项目开发过程中,经常会有需要打包不同版本的 APK 的需求. 比如 debug版,release版,dev版等等. 有时候不同的版本中使用到的不同的服务端api域名也不相同. 比如 debug_ap ...
- DLL编写教程(绝对经典之作)
DLL编写教程 半年不能上网,最近网络终于通了,终于可以更新博客了,写点什么呢?决定最近写一个编程技术系列,其内容是一些通用的编程技术.例如DLL,COM,Socket,多线程等等.这些技术的特点就是 ...
- Android Studio配置(build优化和as优化)
首先是用户目录下的C:\Users\用户名\.gradle\文件下创建gradle.properties文件 输入 org.gradle.daemon=trueorg.gradle.configure ...
- 使用Xib添加自定义View
1.新建Cocoa Touch Class以及UI View,2者同名 2.设置UI View的File's Owner——Custom Class为之前新建类 3.设置Xib中View与类关联 4. ...