いくつかのコマンドを実行するストアドプロシージャがあります。これらのコマンドをストアドプロシージャのトランザクションでラップしたくありません。 4番目のコマンドが失敗した場合、1番目、2番目、3番目のコマンドはロールバックせずにそのままにしておきます。
すべてが1つの大きなトランザクションとして実行されないようにストアドプロシージャを作成することはできますか?
すべてのトランザクションが1つのトランザクションで実行されるわけではありません。この例を見てみましょう:
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;
出力は次のとおりです。
sql_transaction
イベントを監視する拡張イベントセッションを作成すると、dbo.ChangeValues
の実行結果が次のようになります。
この上のスクリーンショットからわかるように、4つのステートメントそれぞれに個別のトランザクションがあります。最初の3つのコミットと最後のコミットは、エラーのためにロールバックします。
batchとtransactionについて、ここで混乱があるかもしれません。
transactionは、1つの単位として成功または失敗するステートメントまたはステートメントのセットです。すべてのDDLステートメントはトランザクション自体にあります(つまり、100行を更新したが、98行がエラーをスローした場合、どの行も更新されません)。 BEGIN TRANSACTION
を使用し、次にCOMMIT
またはROLLBACK
を使用して、一連のステートメントをトランザクションにラップできます。
batchは、一緒に実行される一連のステートメントです。ストアドプロシージャはバッチの例です。ストアドプロシージャでは、1つのステートメントが失敗し、エラートラップ(通常はTRY/CATCH
ブロック)がある場合、後続のステートメントは実行されません。
ストアドプロシージャ自体または外部スコープ(このプロシージャを呼び出すアプリケーションやストアドプロシージャなど)にエラートラップが含まれているため、エラーが発生するとバッチがキャンセルされるのではないかと思います。その場合、エラーをトラップしているスコープでエラーの処理方法を調整する必要があるため、これは解決が難しくなります。
SQLサーバーのすべてがトランザクションに含まれています。
begin transaction
およびend transaction
を明示的に指定すると、それは Explicit Transaction と呼ばれます。そうしない場合、それは Implicit transaction です。
現在のモードを切り替えるには、
set implicit_transactions on
または
set implicit_transactions off
select @@OPTIONS & 2
上記が2を返す場合は、暗黙のトランザクションモードです。 0を返した場合は、自動コミットです。
トランザクションは、データベースを一貫した状態に保つためにALLまたはNothingです。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'
-create SP now-最初の3は成功し、4番目は文字列の切り捨てが原因で失敗することに注意してください...
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
これは、デフォルトでストアドプロシージャが機能する方法です。ストアドプロシージャは、トランザクション内で自動的にラップされません。
ストアドプロシージャが最初のエラーに達したときに停止したい場合は、たとえばコマンド2で問題が発生した場合に、TRY/CATCHログインをそこに戻します。
コマンドごとに個別のトランザクションが必要になります。保存されたトランザクションでこれを行うこともできます。
製品ドキュメントの SAVE TRANSACTION (Transact-SQL)
を参照してください。
すべてのステートメントが暗黙のトランザクションでラップされるため、個々のトランザクションがストアドプロシージャのデフォルトの動作であることを認定したいと思います。ただし、コードの運命を制御するために暗黙のトランザクションに依存するべきではありません。本番用コードでトランザクションを処理する方法を明示的に制御する方がはるかに優れています。