次のパターンの副作用と潜在的な問題に興味があります。
CREATE PROCEDURE [Name]
AS
BEGIN
BEGIN TRANSACTION
BEGIN TRY
[...Perform work, call nested procedures...]
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
RAISERROR [rethrow caught error using @ErrorNumber, @ErrorMessage, etc]
END CATCH
END
私の理解する限りでは、このパターンは単一の手順で使用すると健全です。手順はすべてのステートメントをエラーなしで完了するか、すべてのアクションをロールバックしてエラーを報告します。
ただし、あるストアドプロシージャが別のストアドプロシージャを呼び出して、作業のサブユニットを実行すると(小さなプロシージャが単独で呼び出されることもあることを理解して)、ロールバックに関連する問題が発生します-情報メッセージ(レベル16) The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.
。これは、サブプロシージャで開始されたトランザクションだけでなく、サブプロシージャでのロールバックが常に最も外側のトランザクションをロールバックしているためだと思われます。
エラーが発生した場合(およびエラーがクライアントにSQLエラーとして報告された場合)、すべてをロールバックして中止します。トランザクションをロールバックしようとする外側のレイヤーから生じるすべての副作用についてはわかりません。それはすでにロールバックされています。おそらく@@TRANCOUNT
各TRY CATCHレイヤーでロールバックを行う前に?
最後に、独自のトランザクション層を持つクライアントエンド(Linq2SQL)があります。
try
{
var context = new MyDataContext();
using (var transaction = new TransactionScope())
{
// Some Linq stuff
context.SubmitChanges();
context.MyStoredProcedure();
transactionComplete();
}
}
catch
{
// An error occured!
}
inside MyStoredProcedureと呼ばれるストアドプロシージャ「MySubProcedure」がエラーを発生させた場合、MyStoredProcedureで以前に行われたすべてがロールバックされ、SubmitChangesによって行われたすべてのLinq操作が確実になります。ロールバックし、最後にエラーがログに記録されることを?または、子パーツを個別に使用できるようにしながら、操作全体をアトミックにするためにパターンを変更する必要があります(つまり、サブプロシージャは同じアトミック保護を保持する必要があります)
これがテンプレートです(エラーログは削除されます)
これは処理するように設計されています
説明:
すべてのTXN開始とコミット/ロールバックは、@@TRANCOUNT
が入り口と出口で同じになるようにペアにする必要があります
@@TRANCOUNT
の不一致はエラー266を引き起こします
BEGIN TRAN
インクリメント@@TRANCOUNT
COMMIT
減少@@TRANCOUNT
ROLLBACK
は@@TRANCOUNT
をゼロに戻します
現在のスコープに対して@@TRANCOUNT
をデクリメントすることはできません
これは「内部取引」だと思われます
SET XACT_ABORT ON
は、@@TRANCOUNT
の不一致によるエラー266を抑制します
そして、このような問題も扱います "SQL Server Transaction Timeout" dba.se
これにより、クライアント側のTXN(LINQなど)が可能になります。単一のストアドプロシージャは、分散トランザクションまたはXAトランザクションの一部、または単にクライアントコード(.net TransactionScopeなど)で開始されたものです。
使用法:
概要
コード
CREATE PROCEDURE [Name]
AS
SET XACT_ABORT, NOCOUNT ON
DECLARE @starttrancount int
BEGIN TRY
SELECT @starttrancount = @@TRANCOUNT
IF @starttrancount = 0
BEGIN TRANSACTION
[...Perform work, call nested procedures...]
IF @starttrancount = 0
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 AND @starttrancount = 0
ROLLBACK TRANSACTION;
THROW;
--before SQL Server 2012 use
--RAISERROR [rethrow caught error using @ErrorNumber, @ErrorMessage, etc]
END CATCH
GO
注:
SET XACT_ABORT ON
のため、ロールバックチェックは実際には冗長です。しかし、それは私を気分良くさせ、なしで奇妙に見え、あなたがそれを望まない状況を可能にします
Remus Rusan には、セーブポイントを使用する similar Shell があります。私はアトミックDBコールを好み、彼らの記事のような部分的な更新を使用しません
私はLinqの男ではありません(Erlandでもありません)が、エラー処理に関する絶対的な聖書を書いています。 Linqが問題を悪化させる可能性のある複雑さ以外にも、他のすべての質問に回答する必要があります。
@AlexKuznetsovで言及されているエラー番号と行番号を返す問題を解決するには、次のようにエラーを発生させることができます。
DECLARE @ErrorMessage NVARCHAR(4000)
DECLARE @ErrorSeverity INT
DECLARE @ErrorState INT
DECLARE @ErrorLine INT
DECLARE @ErrorNumber INT
SELECT @ErrorMessage = ERROR_MESSAGE(),
@ErrorSeverity = ERROR_SEVERITY(),
@ErrorState = ERROR_STATE(),
@ErrorNumber = ERROR_NUMBER(),
@ErrorLine = ERROR_LINE()
RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber, @ErrorLine)
-上記の@Amandaメソッドは正しいエラー番号を返しません
DECLARE
@ErrorMessage nvarchar(4000),
@ErrorSeverity int,
@ErrorState int,
@ErrorLine int,
@ErrorNumber int
BEGIN TRY
SELECT 1/0; -- CATCH me
END TRY
BEGIN CATCH
DECLARE @err int = @@ERROR
PRINT @err -- 8134, divide by zero
PRINT ERROR_NUMBER() -- 8134
SELECT
@ErrorMessage = ERROR_MESSAGE(),
@ErrorSeverity = ERROR_SEVERITY(),
@ErrorState = ERROR_STATE(),
@ErrorNumber = ERROR_NUMBER(),
@ErrorLine = ERROR_LINE()
-- error number = 50000 :(
RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber, @ErrorLine)
END CATCH
-- error number = 8134
SELECT 1/0