使用简单更新的计数器列的原子增量

时间:2015-04-29 12:28:50

标签: sql transactions sql-update atomic

我试图了解如何安全地增加计数器列,这可能会被许多用户同时递增(它是移动应用程序的Web API)。

我已经阅读了SO中关于该问题的策略中的热门问题,但我似乎无法弄清楚使用简单的问题:

UPDATE Table SET Counter = Counter + 1  

我已经构建了以下代码示例来尝试获取不一致的值并证明自己只使用这个简单的更新语句并不是一种好的做法:

class Program
{
    static void Main(string[] args)
        {
            List<Task> tasks = new List<Task>();

            for (int i = 0; i < 100; i++)
            {
                Task t = Task.Factory.StartNew(() =>
                {
                    WriteToCounter();
                });

                tasks.Add(t);
            }

            Task.WaitAll(tasks.ToArray());
        }

    static void WriteToCounter()
        {
            string connString = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;

            using (SqlConnection connection = new SqlConnection(connString))
            {
                connection.Open();
                Random rnd = new Random();
                for (int i = 1; i <= 100; i++)
                {
                    int wait = rnd.Next(1, 3);
                    Thread.Sleep(wait);

                    string sql = "UPDATE Table SET Counter = Counter + 1";

                    SqlCommand command = new SqlCommand(sql, connection);
                    command.ExecuteNonQuery();
                }
            }
        }
}

在示例中,我试图模拟许多用户同时访问API并更新计数器的场景。当代码运行时,计数器始终为10000 ,这意味着它是一致的。

测试是否正确模拟了我描述的场景? 如果是这样,为什么我可以在没有任何特殊锁定/交易策略的情况下使用更新语句并且仍能得到一致的结果?

1 个答案:

答案 0 :(得分:2)

如果你只是像这样简单地使用它,那你很好。

问题从以下时间开始:

  • 你添加一个条件 - 大多数条件都没问题,但是要避免基于Counter的过滤,这是一种失去决定论的好方法
  • 您在事务内部进行更新(注意这一点 - 很容易处于实际更新语句范围之外的事务中,如果您使用例如TransactionScope则更是如此)
  • 您组合插入和更新(例如通常的“插入,如果不存在”模式) - 如果您只有一个计数器,这不是问题,但对于多个计数器,很容易陷入此陷阱;没有太难解决,除非你也有删除,那么它就变成了一个完全不同的联盟:)
  • 如果您依赖Counter作为唯一自动递增标识符的值,则
  • 可能。如果你将selectupdate分开,而且{,update 基于 select没有帮助,这显然不起作用 - 不像普通updateselect没有序列化同一行的更新;这是锁定提示的来源),我不确定使用output是否安全。

当然,如果事务隔离级别发生变化,情况可能会大不相同。这实际上是错误的合理原因,因为SQL连接池不会重置事务隔离级别,因此如果您更改它,则需要确保它不会影响您在{{1上执行的任何其他SQL从游泳池中取出。