一尘不染

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

sql

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

我已经阅读了SO中针对该问题的战略中的常见问题,但是我似乎无法弄清楚使用简单方法有什么问题:

UPDATE Table SET Counter = Counter + 1

我构建了以下代码示例,以尝试获取不一致的值,并向自己证明,仅使用此简单的update语句不是一种好习惯:

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 ,这意味着它是一致的。

测试是否正确模拟了我描述的场景?
如果是这样,我怎么能在没有任何特殊的锁定/事务策略的情况下使用update语句并仍然获得一致的结果?


阅读 122

收藏
2021-05-16

共1个答案

一尘不染

如果您只用过这种简单的方法,那很好。

问题从以下时间开始出现:

  • 您添加了一个条件-大多数条件都很好,但请避免基于进行过滤Counter,这是失去确定性的好方法
  • 您在 事务 内部进行更新(请注意这一点-在实际更新语句范围之外的事务中很容易,如果使用eg,则更是如此TransactionScope
  • 您将插入和更新结合在一起(例如,通常的“如果不存在,则插入”模式)-如果您只有一个计数器,那么这不是问题,但是对于多个计数器,很容易陷入此陷阱;不太难解决,除非您也有删除操作,否则它将变成一个完全不同的联赛:)
  • 也许, 如果您依赖于Counter成为唯一的自动递增标识符的价值。如果您将selectand分开,则显然不起作用update(不,update 基于 select无济于事-与plain不同updateselect在同一行中更新未序列化;这是锁定提示出现的地方),我不确定如果使用output是安全的。

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

2021-05-16