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 ...
随机推荐
- watch命令详解(linux)
watch命令详解(linux) 在维护系统时经常需要实时查看系统的运行情况,比如实时的系统连接数之类的.在linux可以通过watch命令,实时监控每一条命令执行的结果动态变化. ...
- K-means算法[聚类算法]
聚类算法k-Means的实现 <?php /* *Kmeans法(聚类算法的实现) */ /* *求误差平方和J */ //----------------------------------- ...
- 从零开始PHP学习 - 第五天
写这个系列文章主要是为了督促自己 每天定时 定量消化一些知识! 同时也为了让需要的人 学到点啥~! 本人技术实在不高!本文中可能会有错误!希望大家发现后能提醒一下我和大家! 偷偷说下 本教程最后的目 ...
- python pdb调试以及sublime3快捷键设置
python pdb调试以及sublime3快捷键设置 pdb调试 如果对gdb比较熟悉的话,pdb就很容易上手.以一个demo快速了解常用的调试命令. def test(a): while True ...
- Garbage
垃圾回收 http://journal.stuffwithstuff.com/2013/12/08/babys-first-garbage-collector/ http://blog.jobbole ...
- QListWidget的QComboBox下拉列表添加复选框及消息处理
要在QComboBox下拉列表项中添加复选框,并进行消息处理,在网上搜索了很久没有找到太多有用的信息和实际的例子,但从中还是找到了一些提示性的资料,根据这些简短的介绍,最终实现了这个功能. QComb ...
- Uber在华从沸点到冰点 搞定这些才能继续走下去
腾讯科技 蒋栩根 5月6日报道 五一前夕,本应坐等笑看小长假带来的喜人业绩的Uber被广州市工商.交委.公安部门的联合检查当头浇了一盆冷水,也被戴上了一顶“涉嫌非法运营”的帽子.而一天前,它才与广物汽 ...
- 【每周一译】愚蠢的指标:Java中使用最多的关键字
此翻译纯属个人爱好,由于水平所限,翻译质量可能较低.网络上可能存在其它翻译版本,原文地址:http://blog.jooq.org/2013/08/26/silly-metrics-the-most- ...
- 【从零学习Python】Ubuntu14.10下Python开发环境配置
1. 前言 近期在研究计算机视觉的一些算法,也刚開始接触linux,试着在ubuntu下用qt+openCV进行开发,感觉还行.可是Python作为在学术领域广为应用的高级解释性语言.其在计算机视觉的 ...
- STL中主要的算法(一)
一.replace() 替换算法将指定元素值替换为新值,使用原型例如以下,将迭代器[first,last)中值为old_value的元素所有替换为new_value值. 函数原型: template ...