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

18

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 revenir 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?

Matthew Steeples
la source

Réponses:

16

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:

entrez la description de l'image ici

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

entrez la description de l'image ici

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.

Thomas Stringer
la source
16

Je pense qu'il peut y avoir une certaine confusion ici à propos d'un lot par rapport à une transaction .

Une 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 en utilisant BEGIN TRANSACTIONpuis ou COMMITou ROLLBACK.

Un lot 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 des TRY/CATCHblocs), 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 processus stocké lui-même ou une étendue externe (comme l'application ou le processus stocké qui appelle cette procédure) contient une erreur de récupération. 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.

JNK
la source
Je n'ai trouvé aucun article disant: "Une procédure de stockage est un exemple de lot". Je pense que la procédure stockée est très similaire au lot, mais ce n'est pas un lot. La principale différence est que SP est garanti d'être compilé à l'avance et prêt à être exécuté plusieurs fois contrairement aux lots. Les similitudes sont les suivantes: - Ils exécutent tous les deux chaque commande à la fois. - Si une commande a échoué, toutes les commandes précédentes sont validées (sauf si elle était en cours d'exécution dans une transaction) - si une commande a échoué, toutes les commandes suivantes ne sont pas exécutées.
Ashi
6

Tout dans le serveur SQL est contenu dans une transaction.

Lorsque vous spécifiez explicitement begin transactionet que end transactioncela s'appelle Transaction explicite . Dans le cas contraire, il s'agit d'une 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éez le 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?

Kin Shah
la source
3

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.

mrdenny
la source
2

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 cette transaction individuelle 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.

Adam Haines
la source
-2

séparez chacune des pièces avec un BEGIN TRAN et vérifiez si la transaction a réussi. s'il a été validé, sinon faites une restauration, car ils s'exécutent tous à partir du même niveau, vous pourrez valider chaque section séparément sans avoir à tout restaurer en cas d'échec.

Pour plus d'informations, vous pouvez consulter: http://msdn.microsoft.com/en-us/library/ms188929.aspx

Toni Kostelac
la source
1
Cela créera-t-il des sous-transactions dans ma procédure stockée? Idéalement, j'aimerais éviter cela si possible
Matthew Steeples
1
Si le SP est appelé à partir d'une transaction, les transactions enregistrées ci-dessus sont la réponse. Si le sp n'est pas appelé avec, alors @mrdenny est correct. Le serveur SQL ne prend pas en charge les transactions imbriquées.
StrayCatDBA
@StrayCatDBA juste pour clarifier .. il y a des transactions imbriquées dans SQL Server, mais elles sont mauvaises. SQL Server vous permet de démarrer des transactions à l'intérieur d'autres transactions - appelées transactions imbriquées. Reportez-vous à sqlskills.com/blogs/paul/… , msdn.microsoft.com/en-us/library/ms189336(v=sql.105).aspx et sqlblog.com/blogs/kalen_delaney/archive/2007/08/13 /…
Kin Shah
2
Pour être clair (et pour les paresseux qui ne veulent pas cliquer sur les liens), vous ne démarrez pas réellement une autre transaction. Aka le titre sur le post de Paul: "Mythe: les transactions imbriquées sont réelles." Ce ne sont pas de vraies transactions. COMMIT dans une transaction imbriquée ne fait rien d'autre que décrémenter @@ TRANCOUNT. Il est vrai que vous n'obtenez pas d'erreur si vous imbriquez BEGIN TRAN / COMMIT, mais c'est différent d'avoir de vraies transitions imbriquées.
StrayCatDBA