2

初めてトランザクションを操作すると、次のコードが機能するようになると思いました。

namespace database
{
    class Program
    {
        static string connString = "Server=ServerName;Database=Demo;Trusted_Connection=True;";
        SqlConnection connection = new SqlConnection(connString);
        static Random r = new Random();


        static void Add()
        {
            try
            {
                Thread.Sleep(r.Next(0, 10));
                using (var trans = new TransactionScope())
                {
                    using (var conn = new SqlConnection(connString))
                    {
                        conn.Open();

                        var count = (int)new SqlCommand("select balance from bank WITH (UPDLOCK) where owner like '%Jan%'", conn).ExecuteScalar();
                        Thread.Sleep(r.Next(0, 10));
                        SqlCommand cmd = new SqlCommand("update bank set balance = " + ++count + "where owner like '%Jan%'", conn);
                        cmd.ExecuteNonQuery();
                    }
                    trans.Complete();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        static void Remove()
        {
            try
            {
                Thread.Sleep(r.Next(0, 10));
                using (var trans = new TransactionScope())
                {
                    using (var conn = new SqlConnection(connString))
                    {
                        conn.Open();

                        var count = (int)new SqlCommand("select balance from bank WITH (UPDLOCK) where owner like '%Jan%'", conn).ExecuteScalar();
                        Thread.Sleep(r.Next(0, 10));
                        SqlCommand cmd = new SqlCommand("update bank set balance = " + --count + "where owner like '%Jan%'", conn);
                        cmd.ExecuteNonQuery();

                    }
                    trans.Complete();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }


        static void Main(string[] args)
        {
            for (int i = 0; i < 5; i++)
            {
                Thread t = new Thread(new ThreadStart(Add));
                t.Start();
            }
            for (int i = 0; i < 5; i++)
            {
                Thread t = new Thread(new ThreadStart(Remove));
                t.Start();
            }
            Console.ReadLine();
        }
    }
}

最後に、100回の加算と100回の減算の後、私のバレーンは開始点である100と同じになると思いましたが、スクリプトを実行するたびに上下に変化し続けます。分離レベルがシリアル化可能であっても。誰か教えてもらえますか?O_o

編集:接続の開閉をトランザクションスコープ内に移動しました。ここでの問題は、「トランザクション(プロセスID XX)が別のプロセスとのロックリソースでデッドロックされ、デッドロックの犠牲者として選択されました。トランザクションを再実行してください」ということです。


Marc Gravell Saidのように:トランザクションスコープ内に接続を配置し、選択クエリにUPDLOCKを追加し、isolationlevelをrepeatableReadに変更することでうまくいきました:)

        static void Add()
        {
            try
            {
                Thread.Sleep(r.Next(0, 10));
                using (var trans = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = IsolationLevel.RepeatableRead }))
                {
                    using (var conn = new SqlConnection(connString))
                    {
                        conn.Open();

                        var count = (int)new SqlCommand("select balance from bank WITH (UPDLOCK) where owner like '%Jan%'", conn).ExecuteScalar();
                        Thread.Sleep(r.Next(0, 10));
                        SqlCommand cmd = new SqlCommand("update bank set balance = " + ++count + "where owner like '%Jan%'", conn);
                        cmd.ExecuteNonQuery();
                    }
                    trans.Complete();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
4

2 に答える 2

8

1:現在、TransactionScope冗長で未使用の可能性があります。トランザクションを変更して接続をラップしてみてください。その逆ではありません(ああ、使用してusingください)。

using (var trans = new TransactionScope(TransactionScopeOption.Required,
      new TransactionOptions() { IsolationLevel = IsolationLevel.Serializable }))
using (var conn = new SqlConnection(connString))
{
    conn.Open();
    //...
    trans.Complete();
}

このように、接続はトランザクション内に正しく参加する必要があります(そして、何か悪いことが起こった場合は適切にクリーンアップされます)

上記が主な問題だと思います; つまり、トランザクションに参加していません。これは、読み取り/書き込み操作が実際にはより高い分離レベルに引き上げられていないため、変更が失われる可能性があることを意味します。

2:ただし、それを単独で行うと、デッドロックが発生することが予想されます。デッドロックを回避するために、更新することがわかっている場合は、それを使用することをお勧めします。(UPDLOCK)これselectにより、開始時に書き込みロックが取得されるため、競合するスレッドがある場合は、デッドロックではなくブロックが取得されます。

明確にするために、このデッドロックシナリオは次の原因で発生します。

  • スレッドAが行を読み取り、読み取りロックを取得します
  • スレッドBが行を読み取り、読み取りロックを取得します
  • スレッドAは行を更新しようとし、Bによってブロックされます
  • スレッドBは行を更新しようとし、Aによってブロックされます

を追加するとUPDLOCK、次のようになります。

  • スレッドAが行を読み取り、書き込みロックを取得します
  • スレッドBは行を読み取ろうとし、Aによってブロックされます
  • スレッドAは行を更新します
  • スレッドAがトランザクションを完了します
  • スレッドBは続行でき、行を読み取り、書き込みロックを取得します
  • スレッドBは行を更新します
  • スレッドBがトランザクションを完了します

3:しかし、些細な更新を行うためのクエリはばかげています。選択せずにインプレースアップデートを発行する方が良いです。update bank set balance = balance + 1 where ...

于 2012-07-24T09:02:16.943 に答える
3

ブロックの接続を開く必要があります。TransactionScope

それ以外の

var conn = new SqlConnection(connString);
conn.Open();
using (var trans = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = IsolationLevel.Serializable }))
{ 
    // do stuff
}

このように使用してください

using (var trans = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = IsolationLevel.Serializable }))
using (var conn =  new SqlConnection(connString))
{ 
    conn.Open();
    // do stuff
}

TransactionScopeこのようにして、接続を開くと、軽量トランザクションとして自動的にに参加します。

あなたはいつでもを見ることができます。

于 2012-07-24T09:17:33.687 に答える