web-dev-qa-db-fra.com

N'utilisez pas de transaction pour la procédure stockée

J'ai une procédure stockée qui exécute quelques commandes. Je ne veux pas que ces commandes soient incluses dans la transaction de la procédure stockée. Si la 4ème commande échoue, je veux que les 1ère, 2ème et 3ème restent et non pas en arrière.

Est-il possible d'écrire la procédure stockée de telle manière qu'elle ne s'exécute pas toutes en une seule grosse transaction?

18
Matthew Steeples

Toutes les transactions ne s'exécuteront pas en une seule transaction. Jetez un œil à cet exemple:

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;

Voici la sortie:

enter image description here

En créant une session d'événements étendus pour surveiller le sql_transaction événement, voici la sortie de l'exécution de dbo.ChangeValues:

enter image description here

Comme vous pouvez le voir dans cette capture d'écran ci-dessus, il existe des transactions distinctes pour chacun des quatre relevés. Les 3 premiers commit, et le dernier annule à cause de l'erreur.

16
Thomas Stringer

Je pense qu'il peut y avoir une certaine confusion ici à propos d'un batch vs un transaction.

A transaction est une instruction ou un ensemble d'instructions qui réussiront ou échoueront en tant qu'unité. Toutes les instructions DDL sont dans les transactions elles-mêmes (c'est-à-dire que si vous mettez à jour 100 lignes mais que la ligne 98 renvoie une erreur, aucune des lignes n'est mise à jour). Vous pouvez également encapsuler une série d'instructions dans une transaction à l'aide de BEGIN TRANSACTION puis COMMIT ou ROLLBACK.

A batch est une série d'instructions qui sont exécutées ensemble. Une procédure stockée est un exemple de lot. Dans une procédure stockée, si une instruction échoue et qu'il y a interception d'erreur (normalement TRY/CATCH blocs), les instructions suivantes ne s'exécuteront pas.

Je soupçonne que votre problème est que le lot est annulé lorsqu'une erreur se produit car le proc stocké lui-même ou une étendue externe (comme l'application ou le proc stocké qui appelle cette procédure) contient une erreur de piégeage. Si tel est le cas, cela est plus difficile à résoudre car vous devez ajuster la façon dont vous gérez les erreurs quelle que soit la portée qui les intercepte.

16
JNK

Tout dans le serveur SQL est contenu dans une transaction.

Lorsque vous spécifiez explicitement begin transaction et end transaction alors on l'appelle Transaction explicite . Si vous ne le faites pas, alors c'est transaction implicite .

Pour changer le mode dans lequel vous vous trouvez, vous utiliseriez

set implicit_transactions on

ou

set implicit_transactions off

select @@OPTIONS & 2

si ci-dessus renvoie 2, vous êtes en mode de transaction implicite. S'il renvoie 0, vous êtes en autocommit.

Une transaction est TOUT ou rien pour garder la base de données dans un état cohérent. N'oubliez pas les propriétés 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'

- créer SP maintenant - notez que les 3 premiers réussiront et que le 4ème échouera en raison de la troncature des chaînes ...

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

Voir: Est-ce une mauvaise pratique de toujours créer une transaction?

8
Kin Shah

C'est ainsi que les procédures stockées fonctionnent par défaut. La procédure stockée n'est pas automatiquement incluse dans une transaction.

Si vous voulez que la procédure stockée s'arrête lorsqu'elle rencontre la première erreur, vous voudrez y mettre une connexion TRY/CATCH pour revenir en cas de problème avec la commande 2 par exemple.

3
mrdenny

Vous aurez besoin de transactions individuelles pour chaque commande. Vous pouvez également accomplir cela avec des transactions enregistrées:

Voir SAVE TRANSACTION (Transact-SQL) dans la documentation du produit.

Je veux qualifier ces transactions individuelles de comportement par défaut pour les procédures stockées, car toutes les instructions sont encapsulées dans des transactions implicites; cependant, personne ne devrait s'appuyer sur des transactions implicites pour contrôler le destin de son code. Il est préférable de contrôler explicitement la façon dont les transactions sont traitées dans le code de production.

2
Adam Haines