不要将事务用于存储过程

Mat*_*les 19 sql-server stored-procedures

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

是否有可能以这样一种方式编写存储过程,使其不全部作为一个大事务执行?

JNK*_*JNK 18

我认为这里可能会有一些关于批处理交易的混淆。

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

一个批次是一系列被一起执行的语句。存储过程是批处理的一个示例。在存储过程中,如果一个语句失败并且存在错误捕获(通常是TRY/CATCH阻塞),那么后续语句将不会执行。

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


Tho*_*ger 17

所有事务都不会在单个事务中执行。看看这个例子:

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;
Run Code Online (Sandbox Code Playgroud)

这是输出:

在此处输入图片说明

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

在此处输入图片说明

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


Kin*_*hah 9

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

当你明确指定begin transactionend transaction则称为明确的事务。当你不这样做时,那就是隐式交易

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

set implicit_transactions on
Run Code Online (Sandbox Code Playgroud)

或者

set implicit_transactions off

select @@OPTIONS & 2
Run Code Online (Sandbox Code Playgroud)

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

事务是全部或什么都可以使数据库保持一致状态..记住 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'
Run Code Online (Sandbox Code Playgroud)

-- 现在创建 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
Run Code Online (Sandbox Code Playgroud)

请参阅:始终创建事务是一种不好的做法吗?


小智 -2

用 BEGIN TRAN 分隔每个部分并检查事务是否成功。如果已提交,则进行回滚,因为它们都从同一级别执行,因此您将能够单独提交每个部分,而无需在失败时回滚所有部分。

有关更多信息,您可以查看:http://msdn.microsoft.com/en-us/library/ms188929.aspx

  • 需要明确的是(对于那些不想点击链接的懒人),您实际上并没有开始另一笔交易。又名 Paul 帖子的标题:“神话:嵌套事务是真实的。” 它们不是真实的交易。嵌套事务中的 COMMIT 除了减少 @@TRANCOUNT 之外不执行任何操作。确实,如果嵌套 BEGIN TRAN/COMMIT,则不会收到错误,但这与真正的嵌套事务不同。 (2认同)