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?
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:
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
:
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.
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.
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?
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.
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.