Não use uma transação para o procedimento armazenado

18

Eu tenho um procedimento armazenado que executa alguns comandos. Não quero que esses comandos sejam agrupados na transação do procedimento armazenado. Se o 4º comando falhar, quero que o 1º, o 2º e o 3º permaneçam, e não a reversão.

É possível gravar o procedimento armazenado de forma que nem todos sejam executados como uma grande transação?

Matthew Steeples
fonte

Respostas:

16

Todas as transações não serão executadas em uma única transação. Veja este exemplo:

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;

Aqui está a saída:

insira a descrição da imagem aqui

Criando uma sessão de Eventos Estendidos para monitorar o sql_transactionevento, eis a saída da execução dbo.ChangeValues:

insira a descrição da imagem aqui

Como você pode ver nesta captura de tela acima, existem transações separadas para cada uma das quatro instruções. Os 3 primeiros confirmam e o último reverte por causa do erro.

Thomas Stringer
fonte
16

Acho que pode haver alguma confusão aqui sobre um lote versus uma transação .

Uma transação é uma declaração ou conjunto de declarações que terão êxito ou falharão como uma unidade. Todas as instruções DDL estão nas próprias transações (ou seja, se você atualizar 100 linhas, mas a linha 98 gerar um erro, nenhuma das linhas será atualizada). Você também pode agrupar uma série de instruções em uma transação usando BEGIN TRANSACTIONe, em seguida, ou COMMITou ROLLBACK.

Um lote é uma série de instruções que são executadas juntas. Um procedimento armazenado é um exemplo de lote. Em um procedimento armazenado, se uma instrução falhar e houver interceptação de erro (normalmente TRY/CATCHbloqueia), as instruções subsequentes não serão executadas.

Suspeito que seu problema é que o lote está sendo cancelado quando ocorre um erro porque o próprio processo armazenado ou um escopo externo (como o aplicativo ou o processo armazenado que chama esse procedimento) possui um erro de interceptação. Se for esse o caso, isso é mais difícil de resolver, pois você precisa ajustar a maneira como lida com os erros em qualquer escopo que os esteja capturando.

JNK
fonte
Não encontrei nenhum artigo que diz: "Um procedimento de armazenamento é um exemplo de lote". Acredito que o procedimento armazenado é muito semelhante ao lote, mas não é um lote. A principal diferença é: o SP é garantido para ser compilado com antecedência e pronto para execução várias vezes, diferentemente dos lotes. As semelhanças são: - Ambos executam cada comando de cada vez. - Se um comando falhar, todos os comandos anteriores serão confirmados (a menos que estejam em execução em uma transação) - se um comando falhar, todos os comandos seguintes não serão executados.
Ashi
6

Tudo no servidor sql está contido em uma transação.

Quando você especifica explicitamente begin transactione end transactioné chamado de transação explícita . Quando você não faz, é uma transação implícita .

Para alternar em qual modo você está, use

set implicit_transactions on

ou

set implicit_transactions off

select @@OPTIONS & 2

se acima retornar 2, você estará no modo de transação implícita. Se retornar 0, você estará na confirmação automática.

Uma transação é TUDO ou nada para manter o banco de dados em um estado consistente. Lembre-se das propriedades 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'

- crie SP agora - observe que os três primeiros serão bem-sucedidos e o quarto falhará devido ao truncamento de strings ...

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

Consulte: É uma prática ruim sempre criar uma transação?

Kin Shah
fonte
3

É assim que os procedimentos armazenados funcionam por padrão. O procedimento armazenado não é quebrado automaticamente em uma transação.

Se você deseja que o procedimento armazenado pare quando ocorrer o primeiro erro, coloque algum login TRY / CATCH lá para retornar no caso de um problema com o comando 2, por exemplo.

Mrdenny
fonte
2

Você precisará de transações individuais para cada comando. Você também pode fazer isso com transações salvas:

Veja SAVE TRANSACTION (Transact-SQL)na documentação do produto.

Eu quero qualificar que transações individuais sejam o comportamento padrão para procedimentos armazenados, porque todas as instruções são agrupadas em transações implícitas; no entanto, ninguém deve confiar em transações implícitas para controlar o destino de seu código. É uma prática muito melhor controlar explicitamente a maneira como as transações são tratadas no código de produção.

Adam Haines
fonte
-2

separe cada uma das partes com um BEGIN TRAN e verifique se a transação foi bem-sucedida. se foi confirmado, caso contrário, faça uma reversão, uma vez que todos estão sendo executados no mesmo nível, você poderá confirmar cada seção separadamente sem precisar reverter tudo se houver uma falha.

Para obter mais informações, consulte: http://msdn.microsoft.com/en-us/library/ms188929.aspx

Toni Kostelac
fonte
11
Isso criará sub-transações dentro do meu Procedimento Armazenado? Idealmente, eu gostaria de evitar que se possível
Matthew Steeples
11
Se o SP for chamado de dentro de uma transação, as transações salvas acima serão a resposta. Se o sp não for chamado com, então @mrdenny está correto. Servidor SQL não suporta transação aninhada.
StrayCatDBA
@StrayCatDBA apenas para esclarecer .. existem transações aninhadas no SQL Server, mas são más. O SQL Server permite iniciar transações dentro de outras transações - chamadas transações aninhadas. Consulte sqlskills.com/blogs/paul/… , msdn.microsoft.com/en-us/library/ms189336(v=sql.105).aspx e sqlblog.com/blogs/kalen_delaney/archive/2007/08/13 / ...
Kin Shah
2
Para ser claro (e para os preguiçosos que não querem clicar nos links), na verdade você não está iniciando outra transação. Também chamado de título no post de Paul: "Mito: transações aninhadas são reais". Eles não são transações reais. COMMIT em uma transação aninhada não faz nada, exceto decremento @@ TRANCOUNT. É verdade que você não obtém um erro se aninhar BEGIN TRAN / COMMIT, mas isso é diferente de ter reais transições aninhadas.
StrayCatDBA