No use una transacción para el procedimiento almacenado

18

Tengo un procedimiento almacenado que ejecuta algunos comandos. No quiero que estos comandos se envuelvan en la transacción del procedimiento almacenado. Si el cuarto comando falla, quiero que el primero, el segundo y el tercero se queden y no retrocedan.

¿Es posible escribir el procedimiento almacenado de tal manera que no todo se ejecute como una gran transacción?

Matthew Steeples
fuente

Respuestas:

16

Todas las transacciones no se ejecutarán en una sola transacción. Echale un vistazo a éste ejemplo:

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;

Aquí está la salida:

ingrese la descripción de la imagen aquí

Al crear una sesión de eventos extendidos para monitorear el sql_transactionevento, aquí está el resultado de la ejecución dbo.ChangeValues:

ingrese la descripción de la imagen aquí

Como puede ver en esta captura de pantalla anterior, hay transacciones separadas para cada una de las cuatro declaraciones. Los primeros 3 se comprometen y el último retrocede debido al error.

Thomas Stringer
fuente
16

Creo que puede haber cierta confusión aquí sobre un lote frente a una transacción .

Una transacción es una declaración o conjunto de declaraciones que tendrán éxito o fallarán como una unidad. Todas las instrucciones DDL están en transacciones mismas (es decir, si actualiza 100 filas pero la fila 98 arroja un error, ninguna de las filas se actualiza). También puede ajustar una serie de declaraciones en una transacción usando BEGIN TRANSACTIONy luego COMMITo ROLLBACK.

Un lote es una serie de declaraciones que se ejecutan juntas. Un procedimiento almacenado es un ejemplo de un lote. En un procedimiento almacenado, si una declaración falla y hay un error de captura (normalmente TRY/CATCHbloques), las siguientes declaraciones no se ejecutarán.

Sospecho que su problema es que el lote se cancela cuando se produce un error porque el proceso almacenado en sí mismo o un ámbito externo (como la aplicación o el proceso almacenado que llama a este procedimiento) tiene un error de captura. Si ese es el caso, es más difícil de resolver ya que debe ajustar la forma en que maneja los errores en cualquier ámbito que los esté atrapando.

JNK
fuente
No encontré ningún artículo que diga: "Un procedimiento de tienda es un ejemplo de un lote". Creo que el procedimiento almacenado es muy similar al lote pero no es un lote. La principal diferencia es: se garantiza que SP se compilará por adelantado y estará listo para su ejecución varias veces, a diferencia de los lotes. Las similitudes son: - Ambos ejecutan cada comando a la vez. - Si falla un comando, se confirman todos los comandos anteriores (a menos que se esté ejecutando en una transacción); si un comando falla, no se ejecutan todos los comandos siguientes.
Ashi
6

Todo en el servidor SQL está contenido en una transacción.

Cuando especifica explícitamente begin transactiony end transactionluego se llama Transacción explícita . Cuando no lo hace, entonces es una transacción implícita .

Para cambiar en qué modo estás, usarías

set implicit_transactions on

o

set implicit_transactions off

select @@OPTIONS & 2

si lo anterior devuelve 2, está en modo de transacción implícita. Si devuelve 0, está en confirmación automática.

Una transacción es TODO o nada para mantener la base de datos en un estado coherente ... recuerde las propiedades de 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'

- cree SP ahora - tenga en cuenta que los primeros 3 tendrán éxito y el cuarto fallará debido al truncamiento de la cadena ...

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: ¿Es una mala práctica crear siempre una transacción?

Kin Shah
fuente
3

Así es como funcionan los procedimientos almacenados de forma predeterminada. El procedimiento almacenado no se ajusta dentro de una transacción automáticamente.

Si desea que el procedimiento almacenado se detenga cuando llegue al primer error, querrá poner algún inicio de sesión TRY / CATCH allí para regresar en caso de un problema con el comando 2, por ejemplo.

mrdenny
fuente
2

Necesitará transacciones individuales para cada comando. También puede lograr esto con transacciones guardadas:

Ver SAVE TRANSACTION (Transact-SQL)en la documentación del producto.

Quiero calificar esas transacciones individuales como el comportamiento predeterminado para los procedimientos almacenados, porque todas las declaraciones están envueltas en transacciones implícitas; sin embargo, nadie debe confiar en transacciones implícitas para controlar el destino de su código. Es una práctica mucho mejor controlar explícitamente la forma en que se manejan las transacciones en el código de producción.

Adam Haines
fuente
-2

separe cada una de las partes con un BEGIN TRAN y verifique si la transacción fue exitosa. si fue confirmarlo, de lo contrario, haga una reversión, ya que todos se están ejecutando desde el mismo nivel, podrá confirmar cada sección por separado sin tener que retroceder todo si falla.

Para obtener más información, puede consultar: http://msdn.microsoft.com/en-us/library/ms188929.aspx

Toni Kostelac
fuente
1
¿Eso creará sub transacciones dentro de mi Procedimiento almacenado? Idealmente me gustaría evitar eso si es posible
Matthew Steeples
1
Si se llama al SP desde una transacción, las transacciones guardadas arriba son la respuesta. Si no se llama a sp, entonces @mrdenny es correcto. El servidor SQL no admite transacciones anidadas.
StrayCatDBA
@StrayCatDBA solo para aclarar ... hay transacciones anidadas en SQL Server, pero son malvadas ... SQL Server le permite iniciar transacciones dentro de otras transacciones, llamadas transacciones anidadas. Consulte sqlskills.com/blogs/paul/… , msdn.microsoft.com/en-us/library/ms189336(v=sql.105).aspx y sqlblog.com/blogs/kalen_delaney/archive/2007/08/13 / ...
Kin Shah
2
Para ser claros (y para los perezosos que no quieren hacer clic en los enlaces), en realidad no están comenzando otra transacción. También conocido como el título en la publicación de Paul: "Mito: las transacciones anidadas son reales". No son transacciones reales. COMMIT en una transacción anidada no hace nada excepto decrementar @@ TRANCOUNT. Es cierto que no obtendrá un error si anida BEGIN TRAN / COMMIT, pero eso es diferente de tener transiciones anidadas reales.
StrayCatDBA