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

Sql 使用简单更新的计数器列的原子增量,sql,transactions,sql-update,atomic,Sql,Transactions,Sql Update,Atomic,我试图理解如何安全地增加计数器列,这可能会被许多用户同时增加(这是一个移动应用程序的Web API) 我已经阅读了SO中的流行问题,以了解处理该问题的策略,但我似乎不知道使用简单的: UPDATE Table SET Counter = Counter + 1 我构建了以下代码示例,以尝试获取不一致的值,并证明仅使用此简单的update语句不是好做法: class Program { static void Main(string[] args) {

我试图理解如何安全地增加计数器列,这可能会被许多用户同时增加(这是一个移动应用程序的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();
                }
            }
        }
}
类程序
{
静态void Main(字符串[]参数)
{
列表任务=新列表();
对于(int i=0;i<100;i++)
{
Task t=Task.Factory.StartNew(()=>
{
WriteToCounter();
});
任务。添加(t);
}
Task.WaitAll(tasks.ToArray());
}
静态void WriteToCounter()
{
string connString=ConfigurationManager.ConnectionString[“DefaultConnection”].ConnectionString;
使用(SqlConnection连接=新的SqlConnection(connString))
{
connection.Open();
随机rnd=新随机();

对于(inti=1;i如果你只使用像这样简单的方法,你就没事了

问题始于:

  • 您添加了一个条件-大多数条件都可以,但是避免基于
    计数器进行过滤,这是丢失确定性的一个好方法
  • 您在事务内部进行更新(注意这一点-在实际update语句范围之外的事务中很容易进行更新,如果您使用例如
    TransactionScope
    ,情况更是如此)
  • 您将插入和更新结合在一起(例如,通常的“如果不存在则插入”模式)-如果您只有一个计数器,这不是问题,但是对于多个计数器,很容易落入此陷阱;不太难解决,除非您也有删除,否则它将成为一个完全不同的联盟:)
  • 如果依赖于
    计数器
    的值作为唯一的自动递增标识符,则可能会出现这种情况。如果将
    选择
    更新
    分开,则显然不起作用(不,
    update
    基于
    select
    没有帮助-与普通的
    update
    不同,
    select
    没有与同一行上的更新一起序列化;这就是锁定提示的原因),我不确定使用
    output
    是否安全
当然,如果事务隔离级别更改,情况可能会大不相同。这实际上是导致错误的合理原因,因为SQL连接池不会重置事务隔离级别,因此如果您更改它,您需要确保它不会影响您在
SqlConnection
tak上执行的任何其他SQL他从游泳池里出来

为什么我可以在没有任何特殊锁定/事务策略的情况下使用update语句,并且仍然获得一致的结果

因为当您使用提供保证的数据库时,会自动获得许多这些特性

例如,每个都在一个事务内运行。在SQL Server中,默认情况下是在模式下运行。在此模式下,如果执行查询且没有打开的事务,则会创建一个新的事务。如果查询完成且没有错误,则会自动提交事务。在另一种称为的模式下,它仍会自动创建一个新的事务如果没有打开的事务,则为w事务,但是否实际执行提交则由用户决定

至于,这里也有相当多的复杂性。有各种形式的锁,试图在允许并发的同时防止出现不一致。事实上,SQL Server有一种专用类型的锁,仅用于
UPDATE
s,旨在确保两次并行尝试
启动日期
相同的资源将被正确序列化(而不是允许尝试重叠和潜在的死锁)


因此,长话短说,您在问题中显示的
更新
是完全有效的。

不知道为什么会被否决。