web-dev-qa-db-ja.com

ストアドプロシージャにトランザクションを使用しない

いくつかのコマンドを実行するストアドプロシージャがあります。これらのコマンドをストアドプロシージャのトランザクションでラップしたくありません。 4番目のコマンドが失敗した場合、1番目、2番目、3番目のコマンドはロールバックせずにそのままにしておきます。

すべてが1つの大きなトランザクションとして実行されないようにストアドプロシージャを作成することはできますか?

18

すべてのトランザクションが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;

出力は次のとおりです。

enter image description here

sql_transactionイベントを監視する拡張イベントセッションを作成すると、dbo.ChangeValuesの実行結果が次のようになります。

enter image description here

この上のスクリーンショットからわかるように、4つのステートメントそれぞれに個別のトランザクションがあります。最初の3つのコミットと最後のコミットは、エラーのためにロールバックします。

16
Thomas Stringer

batchtransactionについて、ここで混乱があるかもしれません。

transactionは、1つの単位として成功または失敗するステートメントまたはステートメントのセットです。すべてのDDLステートメントはトランザクション自体にあります(つまり、100行を更新したが、98行がエラーをスローした場合、どの行も更新されません)。 BEGIN TRANSACTIONを使用し、次にCOMMITまたはROLLBACKを使用して、一連のステートメントをトランザクションにラップできます。

batchは、一緒に実行される一連のステートメントです。ストアドプロシージャはバッチの例です。ストアドプロシージャでは、1つのステートメントが失敗し、エラートラップ(通常はTRY/CATCHブロック)がある場合、後続のステートメントは実行されません。

ストアドプロシージャ自体または外部スコープ(このプロシージャを呼び出すアプリケーションやストアドプロシージャなど)にエラートラップが含まれているため、エラーが発生するとバッチがキャンセルされるのではないかと思います。その場合、エラーをトラップしているスコープでエラーの処理方法を調整する必要があるため、これは解決が難しくなります。

16
JNK

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

参照: 常にトランザクションを作成することは悪い習慣ですか?

8
Kin Shah

これは、デフォルトでストアドプロシージャが機能する方法です。ストアドプロシージャは、トランザクション内で自動的にラップされません。

ストアドプロシージャが最初のエラーに達したときに停止したい場合は、たとえばコマンド2で問題が発生した場合に、TRY/CATCHログインをそこに戻します。

3
mrdenny

コマンドごとに個別のトランザクションが必要になります。保存されたトランザクションでこれを行うこともできます。

製品ドキュメントの SAVE TRANSACTION (Transact-SQL) を参照してください。

すべてのステートメントが暗黙のトランザクションでラップされるため、個々のトランザクションがストアドプロシージャのデフォルトの動作であることを認定したいと思います。ただし、コードの運命を制御するために暗黙のトランザクションに依存するべきではありません。本番用コードでトランザクションを処理する方法を明示的に制御する方がはるかに優れています。

2
Adam Haines