如何在手动递增新行的PK时避免数据库争用情况

时间:2009-03-30 18:10:26

标签: c# sql-server database vb.net asp.net-2.0

我在SQL Server 2005中有一个遗留数据表,它有一个没有身份/自动增量的PK,没有实现它的能力。

因此,我不得不通过“SELECT MAX(id)+ 1 FROM table”-before-insert技术手动在ASP.NET中创建新记录。

显然,如果同时插入,这会在ID上创建竞争条件。

优雅地解决比赛碰撞事件的最佳方法是什么?我正在寻找检测碰撞的VB.NET或C#代码的想法,然后通过获得另一个max(id)+ 1来重新尝试失败的插入。可以这样做吗?

思考?评论?智慧?

谢谢!

注意:如果我无法以任何方式更改数据库怎么办?

7 个答案:

答案 0 :(得分:6)

使用标识列创建辅助表。在事务插入到aux表中,检索该值并使用它插入旧表中。此时,您甚至可以删除在aux表中插入的行,该点只是将其用作递增值的来源。

答案 1 :(得分:4)

无法更改数据库架构非常苛刻。

如果将现有PK插入表中,您将收到SqlException,并显示一条指示PK约束违规的消息。捕获此异常并重试插入几次,直到成功为止。如果您发现碰撞率过高,则可以尝试max(id) + <small-random-int>而不是max(id) + 1。请注意,使用此方法,您的ID将有间隙,ID空间将很快耗尽。

另一种可能的方法是模拟数据库外部的自动增量ID 。例如,每次需要下一个id并使用返回值时,创建一个静态整数Interlocked.Increment。棘手的部分是将此静态计数器初始化为良好的值。我会用Interlocked.CompareExchange

来做
class Autoincrement {
  static int id = -1;
  public static int NextId() {
    if (id == -1) {
      // not initialized - initialize
      int lastId = <select max(id) from db>
      Interlocked.CompareExchange(id, -1, lastId);
    }
    // get next id atomically
    return Interlocked.Increment(id);
  }
}

显然后者只有在通过Autoincrement.NextId单个过程获得所有插入的id时才有效。

答案 2 :(得分:3)

关键是在一个陈述或一个交易中完成。

你能这样做吗?

INSERT (PKcol, col2, col3, ...)
SELECT (SELECT MAX(id) + 1 FROM table WITH (HOLDLOCK, UPDLOCK)), @val2, @val3, ...

如果不进行测试,这可能也会起作用:

INSERT (PKcol, col2, col3, ...)
VALUES ((SELECT MAX(id) + 1 FROM table WITH (HOLDLOCK, UPDLOCK)), @val2, @val3, ...)

如果你不能,另一种方法是在触发器中进行。

  1. 触发器是INSERT事务的一部分
  2. 对MAX使用HOLDLOCK,UPDLOCK。这将保持行锁定直到提交
  3. 正在更新的行被锁定一段时间
  4. 第二个插入将等到第一个插入完成。 缺点是您正在更改主键。

    辅助表需要成为事务的一部分。

    或按建议更改架构......

答案 3 :(得分:3)

注意:您所需要的只是不断增加的整数来源。它不必来自同一个数据库,甚至不必来自数据库。

就个人而言,我会使用SQL Express因为它是免费且容易的。

如果您有一台网络服务器: 使用单个表[ID]在单个自动增量字段[new_id]的Web服务器上创建SQL Express数据库。将记录插入此[ids]表,获取[new_id],并将其作为相关表的PK传递到数据库层。

如果您有多个网络服务器: 设置很痛苦,但你可以通过设置适当的种子/增量来使用相同的技巧(即增量= 3,种子= 1/2/3用于三个Web服务器)。

答案 4 :(得分:2)

如何在可序列化的事务中运行整个批处理(select for id和insert)?

这应该让你需要在数据库中进行更改。

答案 5 :(得分:1)

最佳解决方案是更改数据库。您可能无法将列更改为标识列,但您应该能够确保列上有唯一约束并添加一个新的标识列,其中包含现有PK。然后使用新列或使用触发器使旧列镜像为新列,或两者都使用。

答案 6 :(得分:1)

主要关注的是并发访问吗?我的意思是,您的应用的多个实例(或者,上帝禁止,您控制之外的其他应用)会同时执行插入吗?

如果没有,您可以通过应用中的中央同步模块管理插入,并完全避免竞争条件。

如果是这样,那么......就像乔尔说的那样,改变数据库。我知道你不能,但是这个问题和山丘一样古老,并且在数据库层面得到了很好的解决。如果你想自己修复它,你只需要一遍又一遍地循环(插入,检查冲突,删除)。根本问题是你不能执行一个事务(我不是指在SQL“TRANSACTION”意义上,而是在更大的数据理论意义上),如果你没有来自数据库的支持。

我唯一想到的是,如果您至少可以控制谁有权访问数据库(例如,只有“授权”的应用程序,无论是由您编写还是已批准),您都可以实现边带互斥量排序,所有应用程序共享“说话棒”,并且互斥锁的所有权需要进行插入。那将是它自己毛茸茸的蜡球,因为你必须找出死客户的政策,托管的地方,配置问题等等。当然,一个“流氓”客户端可以做插入而没有说话棒和软管整个设置。