坊やがゆく

iPhoneアプリ

2006-06-08 技術メモ

SQL Server 2000でのコネクション/トランザクション問題


注意 :

本件は以下の環境で発生したものです。

全ての環境で発生するとは限りません。



アプリケーションからストアドを実行する場面で次のようなエラーが発生することがありました。

Code:266

Message:

EXECUTE 後のトランザクション数は、 COMMIT TRAN または ROLLBACK TRAN ステートメントに間違いがあることを示しています。以前の数 = 1、現在の数 = 0 です。


このケースはアプリケーションとストアドでトランザクションがネストしており、ストアド内でROLLBACKが発生した場合です。

アプリケーション

コネクション確立(C1)

トランザクション開始(C1:T1)

  ■ストアド

  トランザクション開始(C1:T2)

  ロールバック(T2) …★例外発生

トランザクション終了(T1)

コネクション切断(C1)


同一のコネクションでトランザクションがネストしているためロールバックに失敗するようです。たとえトランザクション名を指定しても。*1

この場合はセーブポイントを設けてセーブポイント名でロールバック、かつロールバック後にコミットも発行*2することで例外発生を回避できました。


しかし、これではストアドのコードが釈然としない、というか独立性が損なわれるので、出来れば別の解決をしたいものです。


とりあえずトランザクションをネストしないのが一番良いです。というか、始めからそうしろと思うのですが、色んな大人の事情などで今回はそうできませんでした。

ですのでコネクションを別々にすることにしました。構成は次のようになります。

アプリケーション

コネクション確立(C1)

トランザクション開始(C1:T1)

コネクション確立(C2)

  ■ストアド

  トランザクション開始(C2:T2)

  ロールバック(T2) …★問題解消

コネクション切断(C2)

トランザクション終了(T1)

コネクション切断(C1)


これだとストアドの構成は特に変更しなくても良いです。

アプリケーション内でコネクションの確立がネストしていますが、C2の確立〜切断まではモジュールとして独立しているので特に意識しなくても良い(はず)です。


ただしあくまでも別々のコネクションですので、C1で取得したロックの影響でC2が失敗する恐れがある、というかデッドロックなど起こさないよう細心の注意が必要です。

そう考えるとこの解もイマイチだなぁー。

ただ独立した処理だからストアドになってるハズなので大丈夫とは思いますが…。


追記

KHさんのコメントで教えていただいたサイトを見てみました。

・・・英語だ!

でもファンタジスタがパスで会話できるようにプログラマならコードで会話ができるので大丈夫♪

-- Normal exit
IF @@TRANCOUNT > 0
  COMMIT
RETURN(0)

-- Error Exit
ErrExit:
IF @@TRANCOUNT > 1
  COMMIT
ELSE
  IF @@TRANCOUNT = 1
    ROLLBACK
RETURN(-1)

ふみゅ。おバカな私はマトリックスで表さないとよく解りません♪

トランザクション012以上
Normal Exit(NOP)COMMITCOMMIT
Error Exit(NOP)ROLLBACKCOMMIT

トランザクション数が複数の場合はエラーでもCOMMITを発行するのですか!?

ちゃんとストアド内の処理がキャンセルされているのか不安で仕方ありません。

たしかにここでROLLBACKすると例外が発生して困ってたのですけど…。

だ、大丈夫でしたか? > KHさん


結論

KHさんから詳細な検証内容を教えていただきました。

なるほど、これで正しいのですね。

  • 内側のトランザクション
    • Normal Exit時はCOMMIT
    • Error Exit時はCOMMIT(ただし戻り値などで外側のトランザクションにエラーを通知
  • 外側のトランザクション
    • 正常時はCOMMIT
    • エラー時はROLLBACK

最終的な制御は外側のトランザクションが行えば良いということですか。

納得です (^_^) 感謝×2♪


ファンタジスタ (Number 1) (少年サンデーコミックス)

ファンタジスタ (Number 1) (少年サンデーコミックス)

おまえのパスの声を聞かせてくれ

*1:これが解せない

*2:これも解せない

KHKH 2006/08/03 20:36 私も同じエラーで悩んだのですが、
Microsoft SQL Server MVPの、この人のページ
http://www.vfpconversion.com/Article.aspx?quickid=0305111
の解決方法を採用することにしました。

一番下のListing 2(The Multi-Level Model)での方法です。
お試しください。

PoohKidPoohKid 2006/08/04 20:32 KHさん
教えていただきありがとうございます。
英語はとりあえずすっとばしてコードだけ読んだんですけど、エラー時のCOMMITだけ気になってしまいました。
次にコードを触る機会があったら検証してみます。

KHKH 2006/08/08 12:11 検証してみてください♪

--テストテーブルの作成
CREATE TABLE [test] (
[F1] [varchar] (4) NOT NULL ,
[F2] [varchar] (4) NULL ,
PRIMARY KEY CLUSTERED
(
[F1]
) ON [PRIMARY]
) ON [PRIMARY]
GO

ストアドプロシージャ内でトランザクション処理を記述し、

CREATE PROCEDURE dbo.usp_AAA
AS
BEGIN TRANSACTION
INSERT INTO dbo.test
(
F1
,F2
)
VALUES
(
’111’
,’222’
)
RAISERROR (’ERROR’, 16, 1) --無理矢理、例外を発生
GOTO ErrExit
RETURN(0)
ErrExit:
ROLLBACK TRANSACTION
RETURN(-1)
GO

--テストのSQL
DECLARE
@rtn int
BEGIN TRANSACTION
EXECUTE @rtn = dbo.usp_AAA
IF (@rtn = 0)
BEGIN
COMMIT TRANSACTION
END
ELSE
BEGIN
ROLLBACK TRANSACTION
END
GO

上記SQLを実行すると、ストアドプロシージャusp_AAAの

ErrExit:
ROLLBACK TRANSACTION ←ここでエラーが発生
RETURN(-1)

--ネストしたトランザクション対応版
ALTER PROCEDURE usp_AAA
AS
BEGIN TRANSACTION
INSERT INTO dbo.test
(
F1
,F2
)
VALUES
(
’111’
,’222’
)
RAISERROR (’ERROR’, 16, 1) --無理矢理、例外を発生
GOTO ErrExit
--トランザクションのネスト対応
IF @@TRANCOUNT > 0
BEGIN
COMMIT TRANSACTION
END
RETURN(0)
ErrExit:
--トランザクションのネスト対応
IF @@TRANCOUNT > 1 --外側でトランザクションが開始されている場合
BEGIN
--ここではCOMMITし、ROLLBACKを外側に任せる
COMMIT TRANSACTION
END
ELSE IF @@TRANCOUNT = 1 --ローカルトランザクションのみの場合
BEGIN
ROLLBACK TRANSACTION --普通にROLLBACK
END
RETURN(-1)
GO

ここで、テストのSQLを実行すると、

テストのSQLのように、外側のトランザクションで処理してあげることで、
正常に動作します。(上記例の場合は、必ずROLLBACKですが。)

ネストしたトランザクションの場合、そのCOMMITやROLLBACKは
一番外側のCOMMITやROLLBACKに依存するということだと思います。

大丈夫です♪

PoohKidPoohKid 2006/08/08 14:58 おおお、こんなに詳しく! どうもありがとうございます!!
なるほど、ネスト中のトランザクションではROLLBACKは発行せずにCOMMITで終了して戻り値で判断するのですね。
そしてROLLBACKは外側のトランザクションで行う、と。

大変勉強になりました。感謝です♪

KHKH 2006/08/09 00:20 ちなみに、私の場合は、
SQL Server 2005
VisualStudio .NET 2005
でした。
私も解せなかったのですが、
どうやら、これが本来の動作っぽいですね。
私も勉強になりました♪

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/PoohKid/20060608/p2