为什么这个事务管理器不起作用?

use*_*085 1 c# sql-server transactions transactionscope

我第一次玩交易我以为我会得到以下代码:

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我假设最后在100次加法和100次减法之后,我的balane将与我的起点相同 - 100,但是每次我运行脚本时它都会不断变化.即使使用isolationlevel可序列化.谁能告诉我为什么?O_O

编辑:将连接打开和关闭移动到事务范围内.现在的问题是我得到"事务(进程ID XX)与另一个进程锁定资源上的死锁,并被选为死锁牺牲品.重新运行事务"


像Marc Gravell Said一样:将连接放在事务范围内并将UPDLOCK添加到select查询并结合更改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);
            }
        }
Run Code Online (Sandbox Code Playgroud)

Mar*_*ell 7

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();
}
Run Code Online (Sandbox Code Playgroud)

这样,连接应该在事务中正确登记(并且如果发生了不好的事情,可以正确清理)

我认为以上是主要问题 ; 即没有参与交易.这意味着可能会丢失更改,因为读/写操作实际上并未提升到更高的隔离级别.

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 ...