不要将事务用于存储过程


18

我有一个运行一些命令的存储过程。我不希望这些命令被包装在存储过程的事务中。如果第4条命令失败,我希望保留第1条,第2条和第3条而不回滚。

是否可以编写存储过程,使其不能全部作为一个大事务来执行?

Answers:


16

所有交易都不会在单个交易中执行。看一下这个例子:

use TestDB;
go

if exists (select 1 from sys.tables where object_id = object_id('dbo.TestTranTable1'))
    drop table dbo.TestTranTable1;
create table dbo.TestTranTable1
(
    id int identity(1, 1) not null,
    some_int int not null
        default 1
);
go

insert into dbo.TestTranTable1
default values;
go 4

select *
from dbo.TestTranTable1;

if exists (select 1 from sys.sql_modules where object_id = object_id('dbo.ChangeValues'))
begin
    drop proc dbo.ChangeValues;
end
go
create proc dbo.ChangeValues
as
    update dbo.TestTranTable1
    set some_int = 11
    where id = 1;

    update dbo.TestTranTable1
    set some_int = 12
    where id = 2;

    update dbo.TestTranTable1
    set some_int = 13
    where id = 3;

    -- this will error out (arithmetic overflow)
    update dbo.TestTranTable1
    set some_int = 2147483648
    where id = 4;
go

exec dbo.ChangeValues;

select *
from dbo.TestTranTable1;

这是输出:

在此处输入图片说明

通过创建扩展事件会话来监视sql_transaction事件,以下是执行的输出dbo.ChangeValues

在此处输入图片说明

如您在上面的屏幕截图中所见,四个语句中的每一个都有单独的事务。前3次提交,最后一次由于错误而回滚。


16

我认为这可能是关于批处理还是事务的混淆。

一个交易是声明,要么成功,要么失败,因为一个单位的语句或集。所有DDL语句本身都在事务中(即,如果您更新100行,但第98行抛出错误,则不会更新任何行)。您也可以使用BEGIN TRANSACTION,然后使用COMMIT或将一系列语句包装在事务中ROLLBACK

一个批次是一系列被一起执行的语句。存储过程是批处理的一个示例。在存储过程中,如果一个语句失败并且存在错误陷阱(通常为TRY/CATCH块),则后续语句将不执行。

我怀疑您的问题是发生错误时批处理将被取消,因为存储的proc本身或外部作用域(例如调用此过程的应用程序或存储的proc)都存在错误陷阱。如果是这种情况,则解决起来就比较棘手,因为您需要在捕获错误的任何范围内调整错误的处理方式。


我没有找到任何文章说“存储过程就是批处理的例子”。我相信存储过程与批处理非常相似,但它不是批处理。主要区别在于:与批处理不同,保证SP可以提前编译并可以多次执行。相似之处是:-它们都一次执行每个命令。-如果一个命令失败,则所有先前的命令都将提交(除非它正在事务中运行)-如果一个命令失败,则不执行所有下一个命令。
阿什(Ashi)

6

sql server中的所有内容都包含在一个事务中。

当您显式指定时begin transactionend transaction它称为Explicit Transaction。当您不这样做时,它就是隐式事务

要切换您所处的模式,请使用

set implicit_transactions on

要么

set implicit_transactions off

select @@OPTIONS & 2

如果以上返回2,则您处于隐式事务模式。如果返回0,则表示您处于自动提交状态。

事务是ALL还是什么都不会使数据库保持一致状态..记住ACID属性。

CREATE TABLE [dbo].[Products](
    [ProductID] [int] NOT NULL,
    [ProductName] [varchar](25) NULL,
    [DatabaseName] [sysname] NOT NULL,
 CONSTRAINT [pk_Product_ID_ServerName] PRIMARY KEY CLUSTERED 
(
    [ProductID] ASC,
    [DatabaseName] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO


-- insert some data 
INSERT INTO [dbo].[Products]([ProductID], [ProductName], [DatabaseName])
SELECT 1, N'repl1_product1', N'repl1' UNION ALL
SELECT 1, N'repl2_product1_02', N'repl2' UNION ALL
SELECT 1, N'repl3_product1_03', N'repl3' UNION ALL
SELECT 2, N'repl1_product1_01', N'repl1' UNION ALL
SELECT 2, N'repl2_product1', N'repl2' UNION ALL
SELECT 2, N'repl3_product1_03', N'repl3' UNION ALL
SELECT 3, N'repl1_product1_01', N'repl1' UNION ALL
SELECT 3, N'repl2_product1_02', N'repl2' UNION ALL
SELECT 3, N'repl3_product1', N'repl3' UNION ALL
SELECT 4, N'repl1_product1_01', N'repl1' UNION ALL
SELECT 4, N'repl2_product1_02', N'repl2' UNION ALL
SELECT 5, N'repl1_product1_01', N'repl1' UNION ALL
SELECT 5, N'repl2_product1_02', N'repl2'

-立即创建SP-注意前3个将成功,第4个将由于字符串截断而失败...

IF OBJECT_ID ('usp_UpdateProducts', 'P') IS NOT NULL
    DROP PROCEDURE usp_UpdateProducts;
GO
create procedure usp_UpdateProducts
as 
begin try
update Products 
set ProductName = 'repl1_product1'
where DatabaseName = 'repl1'and ProductID = 1;
update Products
set ProductName = 'repl2_product1'
where DatabaseName = 'repl2' and ProductID = 2;
update Products
set ProductName = 'repl3_product1'
where DatabaseName = 'repl3' and ProductID = 3;
update Products
set ProductName = 'repl3_product1_03&&&&&&&&&&39399338492w9924389234923482' -- this will fail ...
where DatabaseName = 'repl3' and ProductID = 4;
SELECT 1/0;
end try
begin catch
SELECT 
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_SEVERITY() AS ErrorSeverity,
        ERROR_STATE() as ErrorState,
        ERROR_PROCEDURE() as ErrorProcedure,
        ERROR_LINE() as ErrorLine,
        ERROR_MESSAGE() as ErrorMessage;
end catch
go

请参阅:总是创建交易是否有坏习惯?


3

默认情况下,这就是存储过程的工作方式。存储过程不会自动包装在事务中。

如果要使存储过程在遇到第一个错误时停止运行,则需要在其中放置一些TRY / CATCH登录名,以在例如命令2出现问题时返回。


2

每个命令将需要单独的事务。您还可以通过保存的事务来完成此任务:

请参阅SAVE TRANSACTION (Transact-SQL)产品文档。

我想将单个事务限定为存储过程的默认行为,因为所有语句都包装在隐式事务中。但是,没有人应该依靠隐式事务来控制其代码的命运。显式控制生产代码中处理事务的方式是一种更好的做法。


-2

使用BEGIN TRAN分开每个部分,并检查交易是否成功。如果是提交,则进行回滚,因为它们都是从同一级别执行的,因此您可以分别提交每个节,而不必在失败的情况下回滚所有节。

有关更多信息,请参见:http : //msdn.microsoft.com/zh-cn/library/ms188929.aspx


1
这会在我的存储过程中创建子交易吗?理想情况下,如果可能的话,我希望避免这种情况
Matthew Steeples 2013年

1
如果从事务中调用SP,那么上面保存的事务就是答案。如果未使用sp调用,则@mrdenny是正确的。SQL Server不支持嵌套事务。
StrayCatDBA

@StrayCatDBA只是为了澄清.. SQL Server中有嵌套事务,但是它们很邪恶。.SQL Server允许您在其他事务内部启动事务-称为嵌套事务。请参阅sqlskills.com/blogs/paul/...msdn.microsoft.com/en-us/library/ms189336(v=sql.105).aspxsqlblog.com/blogs/kalen_delaney/archive/2007/08/13 /…
Kin Shah

2
要清楚(对于不想单击链接的懒惰者),您实际上并没有在开始另一笔交易。保罗在帖子中的标题是:“神话:嵌套交易是真实的。” 它们不是真正的交易。嵌套事务中的COMMIT除了递减@@ TRANCOUNT之外不执行任何操作。的确,嵌套BEGIN TRAN / COMMIT不会出错,但这与真正的嵌套转换不同。
StrayCatDBA
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.