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

Mat*_*ino 6 c# database vb.net sql-server asp.net-2.0

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

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

显然,如果同时插入,这会在ID上产生竞争条件.

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

思考?评论?智慧?

谢谢!

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

Otá*_*cio 6

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


Con*_*tin 4

无法更改数据库模式是很痛苦的。

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

另一种可能的方法是在数据库外部模拟自动增量 id 。例如,创建一个静态整数,Interlocked.Increment每次需要下一个 id 时它都会使用返回值。棘手的部分是将这个静态计数器初始化为合适的值。我会这样做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);
  }
}
Run Code Online (Sandbox Code Playgroud)

显然,后者仅在所有插入的 id 都是通过Autoincrement.NextId单个进程获取时才有效。