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 ...
随机推荐
- jQuery入门第二
element选择器 在文具盒中,有铅笔.钢笔和水彩笔,类似于页面中的<div>.<span>各个元素,虽然同属于一个容器,但有各自的功能,jQuery中可以根据元素名查找元 ...
- Common Git command and mean (Windows)
Config: git config --system git config --global git config --global merge.tool vimdiff Check config: ...
- Oracle的TPCC测试,原来也是个作弊的东西...
http://www.oaktable.net/content/sorted-hash-clusters-rip 根据Jonathan Lewis老先生的测试实例,发觉cluster 的sort功能, ...
- CSS 常用自定义样式
目录: 1. 文本单行显示,并对超出部分截断以省略号代替: 2.列布局或栅格布局:比如:左侧固定宽度,右侧占满剩下的宽度: 章节: 1. 文本单行显示,并对超出部分截断以省略号代替:参见以下代码: d ...
- Database(Mysql)发版控制二
author:skate time:2014/08/18 Database(Mysql)发版控制 The Liquibase Tool related Database 一.Installation ...
- How draw a stem -and -leaf & box-plot display by R.or Python
参考: 使用R进行数据可视化套路之-茎叶图.盒形图 step by step R 读取数据 在网上下载的2013全国各省区GDP排名(exl文件) 先 另存为 data.txt(为了方便存到D盘文件夹 ...
- 四核网络机顶盒芯片局势分析(开放市场):rk3128将会成为四核主流
开放市场:不包含小米.乐视等大品牌闭环生态系统的市场. 今年四核网络播放器以全志的a31s独领风骚.英菲克以绝对优势率先各大白牌品牌公司.只是随着时间的推移,全志的a31s不适应市场主流.因为芯片没有 ...
- [置顶] ios 在一定选项范围随机选取选项demo
原创文章,转载请注明出处:http://blog.csdn.net/donny_zhang/article/details/9408285 demo功能:ios 在一定范围随机选取demo,如截屏.在 ...
- 关于TableViewCell高度自适应问题的整理
TableViewCell高度自适应在网上有很多资料,我只想找出最最最简单的一种方法. 首先梳理一下思路.说到TableViewCell我们第一个想到的问题或许就是cell的复用问题. 1. [se ...
- JS中的replace方法
JavaScript中replace() 方法如果直接用str.replace("-","!") 只会替换第一个匹配的字符. 而str.replace(/\-/ ...