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 次减法之后,我的 balane 将与我的起点相同 - 100,但是每次运行脚本时它都会上下变化。即使隔离级别可序列化。谁能告诉我为什么?o_o

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


就像 Marc Gravell 所说:将连接放在事务范围内并将 UPDLOCK 添加到选择查询中,并结合将隔离级别更改为可重复读取就可以了 :)

        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 回答