我试图了解如何安全地增加计数器列,这可能会被许多用户同时递增(它是移动应用程序的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 ,这意味着它是一致的。
测试是否正确模拟了我描述的场景? 如果是这样,为什么我可以在没有任何特殊锁定/交易策略的情况下使用更新语句并且仍能得到一致的结果?
答案 0 :(得分:2)
如果你只是像这样简单地使用它,那你很好。
问题从以下时间开始:
Counter
的过滤,这是一种失去决定论的好方法TransactionScope
则更是如此)Counter
作为唯一自动递增标识符的值,则select
和update
分开,而且{,update
基于 select
没有帮助,这显然不起作用 - 不像普通update
,select
没有序列化同一行的更新;这是锁定提示的来源),我不确定使用output
是否安全。当然,如果事务隔离级别发生变化,情况可能会大不相同。这实际上是错误的合理原因,因为SQL连接池不会重置事务隔离级别,因此如果您更改它,则需要确保它不会影响您在{{1上执行的任何其他SQL从游泳池中取出。