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 ...
随机推荐
- ElaticSearch网站
http://www.tuicool.com/articles/r2QJVr http://so.searchtech.pro/articles/2013/06/16/1371392427213.ht ...
- 也许有用(也谈VC中ModifyStyle&ModifyStyleEx无法改变控件的Style)
一个View中用到了一个CListCtrl,在OnInitialUpdate函数里面他调用了m_listCtrl.ModifyStyleEx(0, LVS_EX_FULLROWSELECT);但是结 ...
- 阿里云Ubuntu部署java web(2) - 配置tomcat
系统版本号:Ubuntu12.04 64位 安装: 首先要安装java(測试时使用的版本号是6b27-1.12.6-1ubuntu0.12.04.2).版本号可自行选择,但不同版本号配置方法可能不同. ...
- HTTP请求和数据安全
/*------------------------------------- 01 HTTP请求 ---------------------------------------*/ 重点:1.超文本 ...
- Javaweb统计在线人数的小栗子
最近在学习Javaweb相关的内容(不黑不吹之前对web开发零基础),下面通过一个统计在线人数的小栗子讲讲Servlet监听器吧 开发环境 eclipse tomcat 7 先说说这个小栗子的构思: ...
- BZOJ 1624: [Usaco2008 Open] Clear And Present Danger 寻宝之路( 最短路 )
直接floyd.. ----------------------------------------------------------------------- #include<cstdio ...
- 常用类库之.NET中的字符串
字符串的特性 .不可变性 由于字符串是不可变的的,每次修改字符串,都是创建了一个单独字符串副本(拷贝了一个字符串副本).之所以发生改变只是因为指向了一块新的地址. .字符串池(只针对字符串常量) 当一 ...
- 提高PHP编程效率
1.如果能将类的方法定义成static,就尽量定义成static,它的速度会提升将近4倍. 2.$row['id']的速度是$row[id]的7倍. 3.echo比print快,并且使用echo的多重 ...
- poj 3258 River Hopscotch 二分
/** 大意:给定n个点,删除其中的m个点,其中两点之间距离最小的最大值 思路: 二分最小值的最大值---〉t,若有距离小于t,则可以将前面的节点删除:若节点大于t,则继续往下查看 若删除的节点大于m ...
- CentOS修改IP
编辑 /etc/sysconfig/network-scripts/ifcfg-eth0 然后 service network restart DEVICE=eth0BOOTPROTO=noneNM_ ...