techbank.jp コミュニティ

     あれ?どうやるんだっけ?を解決する

坊やがゆくtech

  • トランザクションのネストと@@TRANCOUNTの関係、コミット/ロールバックについてまとめ

    Microsoft TechNet「トランザクションのネスト」にて簡潔に説明されているので引用させていただきます。

    @@TRANCOUNTのカウント(ざっくり)

    • BEGIN TRANSACTION ステートメントは @@TRANCOUNT を 1 ずつ増やします。
    • COMMIT TRANSACTION は @@TRANCOUNT を 1 ずつ減らします。
    • ROLLBACK TRANSACTION ステートメントは、ネストしたトランザクションがすべてロールバックされ、@@TRANCOUNT は 0 になります。
    • @@TRANCOUNT が 0 の場合は、トランザクションにいません。
    http://technet.microsoft.com/ja-jp/library/aa213076(SQL.80).aspx

    トランザクションの外側と内側の概念、超重要!

    Microsoft SQL Server は、内部トランザクションのコミットを無視します。
    トランザクションは、最も外側のトランザクションが終了したときの処理に基づいてコミットまたはロールバックされます。
    外側のトランザクションがコミットされた場合、ネストした内部トランザクションもコミットされます。
    外側のトランザクションがロールバックされた場合、内部トランザクションが個々にコミットされているかどうかに関係なく、内部トランザクションもすべてがロールバックされます。

    http://technet.microsoft.com/ja-jp/library/aa213076(SQL.80).aspx

    つまり一番外側のコミット/ロールバックに依存するということです。
    ※部分的にロールバックしたい場合はセーブポイントを使用します。

    図説およびサンプルコード(英語)
    http://www.vfpconversion.com/Article.aspx?quickid=0305111

    内側のトランザクションのエラー処理などはクセがあるので上記のサンプルコードを参考にされると良いでしょう。
    具体的には「内側のトランザクションではROLLBACKは発行せずにCOMMITで終了して戻り値で判断する。そしてROLLBACKは外側のトランザクションで行う。」です。

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

    参考:
    SQL Server 2000でのコネクション/トランザクション問題
    http://d.hatena.ne.jp/PoohKid/20060608/p2

  • クエリ・エディタでもトランザクションを使おう

    SQL Server Management Studioのクエリ・エディタはちょっとしたSQLを試すのに便利ですね。
    でも更新系のSQLをいきなり叩いて後悔したことありませんか?
    クエリ・エディタではTransact-SQLコマンドを記述できるのでトランザクションを使わない手はありません♪


    トランザクション開始(TRANでも可)

    BEGIN TRANSACTION
    

    ロールバック/コミット

    ROLLBACK TRANSACTION
    COMMIT TRANSACTION
    

    現在の接続でアクティブなトランザクション数を返します

    SELECT @@TRANCOUNT
    

    クエリ・エディタは文字を選択すれば選択した範囲のみ実行するので予めコメントとして用意しておき必要に応じて選択して実行すると便利。

    --トランザクション開始
    --BEGIN TRANSACTION
    
    --ロールバック/コミット
    --ROLLBACK TRANSACTION
    --COMMIT TRANSACTION
    
    --現在の接続でアクティブなトランザクション数を返します
    --SELECT @@TRANCOUNT
    

    もしくは、こうしておけばうっかり全て実行してしまっても安心♪
    (更新系の処理のためにタイムスタンプを変数に格納するコードも置いておきました)

    --トランザクション開始
    BEGIN TRANSACTION
    
    --現在の接続でアクティブなトランザクション数を返します
    --SELECT @@TRANCOUNT
    
    --▼ここにSQLを書く
    --DECLARE @dt DATETIME
    --SELECT @dt = GETDATE() -- SET @dt = GETDATE() でも可
    --▲
    
    --ロールバック/コミット
    ROLLBACK TRANSACTION
    --COMMIT TRANSACTION
    

    参考:
    ちょー基本で簡単に、SQLServerでトランザクションの効果を確認する

  • TransactionScope中に例外が発生したのにロールバックされない

    using System;
    using System.Collections.Generic;
    using System.Text;
    
    using System.Data;
    using System.Data.SQLite;
    using System.Transactions;
    
    namespace ConsoleApplication1
    {
      class Program
      {
          static void Main(string[] args)
          {
              using (SQLiteConnection cnn = new SQLiteConnection("Data Source=:memory:")) // SQLiteはデフォルトで自動コミットモード
              {
                  cnn.Open(); // この時点でトランザクションスコープに含まれる必要がある
    
                  using (TransactionScope tran = new TransactionScope())
                  {
                      try
                      {
                          using (SQLiteCommand cmd = cnn.CreateCommand())
                          {
                              cmd.CommandText = "CREATE TABLE EMPLOYEES (ID INTEGER PRIMARY KEY, NAME NVARCHAR(256))";
                              cmd.ExecuteNonQuery();
    
                              cmd.CommandText = "INSERT INTO EMPLOYEES (NAME) VALUES('HOGE')";
                              cmd.ExecuteNonQuery();
    
                              throw new Exception("ロールバックしたい!");
                          }
                          tran.Complete(); // 到達しない
                      }
                      catch { }
                  }
                  using (SQLiteCommand cmd = cnn.CreateCommand())
                  {
                      cmd.CommandText = "SELECT COUNT(*) FROM EMPLOYEES";
                      SQLiteDataAdapter adapter = new SQLiteDataAdapter(cmd);
                      DataSet ds = new DataSet();
                      adapter.Fill(ds);
                      Console.WriteLine(ds.Tables[0].Rows[0][0]); // 1 (ロールバックされていない)
                  }
              }
              Console.ReadKey();
          }
      }
    }
    

    上記のコードでは例外でTransactionScopeを抜けたにも関わらず更新がロールバックされません。

    TransactionScopeのusingは一見トランザクションを開始しているように見えますが、このコードを実際にプロファイルで確認するとトランザクションは開始されていません。

    実際にトランザクションの対象となるのはConnectionであり、TransactionScope内のConnection.Open()がトランザクションに参加するようです。
    ですのでサンプルのコードはConnection.Open()がTransactionScopeの外側にあるためトランザクションに参加しておらず、自動コミットモードで更新が行われているためロールバックされないのです。

    なのでこれはNGで

    using (SQLiteConnection cnn = new SQLiteConnection("Data Source=:memory:"))
    {
      cnn.Open(); // NG
      using (TransactionScope tran = new TransactionScope())
      {
          tran.Complete();
      }
    }
    

    これはOK

    using (TransactionScope tran = new TransactionScope())
    using (SQLiteConnection cnn = new SQLiteConnection("Data Source=:memory:"))
    {
      cnn.Open(); // OK
      tran.Complete();
    }
    

    教訓:ConnectionはTransactionScopeの内側に書く(定型文!)

  • 【.NET】ゼロから5分でつくれるデータベースプログラミング【SQLite】

    ※ただしVisualStudioはインストール済み

    ちょっとしたサンプルを作りたいときにデータベースエンジンを用意しないで済むので手軽です。
    さらにオンメモリに展開するのでSQLiteファイルのパスを気にする必要もありません。
    (その代わり揮発性ですが^^;)

    サンプル作成手順:

    1. System.Data.SQLiteのサイトからインストーラをダウンロード
    2. インストール
    3. VisualStudioで新規コンソールアプリケーションを作成(デフォルトで可)
    4. インストール先のbinにあるdllを参照に追加
    5. System.Transactionsを参照に追加

    こんな感じで更新や参照を行えます。

    using System;
    using System.Collections.Generic;
    using System.Text;
    
    using System.Data;
    using System.Data.SQLite;
    using System.Transactions;
    
    namespace ConsoleApplication1
    {
      class Program
      {
          static void Main(string[] args)
          {
              using (TransactionScope tran = new TransactionScope())
              using (SQLiteConnection cnn = new SQLiteConnection("Data Source=:memory:"))
              {
                  cnn.Open();
    
                  using (SQLiteCommand cmd = cnn.CreateCommand())
                  {
                      cmd.CommandText = "CREATE TABLE EMPLOYEES (ID INTEGER PRIMARY KEY, NAME NVARCHAR(256))";
                      cmd.ExecuteNonQuery();
    
                      cmd.CommandText = "INSERT INTO EMPLOYEES (NAME) VALUES('HOGE')";
                      cmd.ExecuteNonQuery();
    
                      cmd.CommandText = "SELECT * FROM EMPLOYEES";
                      SQLiteDataAdapter adapter = new SQLiteDataAdapter(cmd);
                      DataSet ds = new DataSet();
                      adapter.Fill(ds);
                      foreach (DataRow row in ds.Tables[0].Rows)
                      {
                          Console.WriteLine(String.Format("ID = {0}, NAME = {1}", row["ID"], row["NAME"])); // ID = 1, NAME = HOGE
                      }
                  }
                  tran.Complete();
              }
              Console.ReadKey();
          }
      }
    }
    

    参考:
    C#からSQLiteを使ってみた

  • はじめまして

    縁あってこちらでブログを書くことになりましたPoohKidと申します。

    けろ-みおさんご紹介いただきましてありがとうございます。
    なんかハードル上がったようなw

    正直なところ客観的に見て私のレベルは中の上(たぶん)。
    スーパーなプログラマではありませんが現場ではそれなりに使えると言っていただけてるようです。

    一言で表すなら
    低いテンション、中ぐらいの能力。

    ですので
    過度な期待はしないでください。

    内容は主に.NETの話題になる予定です。
    フツーのプログラマが日々の疑問や悩みから得た物を普通に語れたらいいなと思います。
    生暖かい目で見守っていただけたら幸いです♪

techbank.jp