2

我正在构建一个使用 LINQ to SQL 的 ASP.NET MVC 2 站点。在我的网站访问数据库的地方之一,我认为可能存在竞争条件。


数据库架构

以下是相关数据库表的一些列,名为Revisions

  • RevisionID - bigint、IDENTITY、PK
  • PostID - bigint, FK to PK of Poststable
  • EditNumber - 整数
  • 修订文本- nvarchar(最大)

在我的网站上,用户可以提交帖子并稍后编辑帖子。原始发帖人以外的用户可以编辑帖子 - 因此可以同时对单个帖子进行多次编辑。

提交 Post 时,会在表中创建一条记录,并在Posts表中创建一条记录,Revisions其中PostID设置为记录的 ID PostsRevisionText设置为 Post 文本,EditNumber 设置为 1。

编辑帖子时,仅Revisions创建一条记录,其中EditNumber设置为比最新编辑编号高 1

因此,EditNumber列指的是帖子被编辑了多少次。


递增EditNumber

我在实现这些函数时看到的挑战是增加EditNumber列。由于该列不能是IDENTITY,我必须手动操作它的值。

这是我的 LINQ 查询,用于确定新修订版应具有的EditNumber :

using(var db = new DBDataContext())
{
    var rev = new Revision();
    rev.EditNumber = db.Revisions.Where(r => r.PostID == postID).Max(r => r.EditNumber) + 1;

    // ... (fill other properties)

    db.Revisions.InsertOnSubmit(rev);
    db.SubmitChanges();
}

计算最大值并增加它可能会导致竞争条件

有没有更好的方法来实现该功能?

4

5 回答 5

6

直接在数据库中更新并返回新的修订:

update Revisions
set EditNumber += 1
output INSERTED.EditNumber
where PostID = @postId;

不幸的是,这在 LINQ 中是不可能的。事实上,在客户端根本不可能无论使用什么技术,都做不到悲观锁,有太多缺点值得考虑。

更新:

以下是我将如何插入新修订版(包括第一个修订版):

create procedure usp_insertPostRevision
  @postId int,
  @text nvarchar(max),
  @revisionId bigint output

as 
begin
  set nocount on;
  declare @nextEditNumber (EditNumber int not null);
  declare @rc int = 0;

  begin transaction;
  begin try
    update Posts
    set LastRevision += 1
    output INSERTED.LastRevision
       into @nextEditNumber (EditNumber)
    where PostId = @postId;

    set @rc = @@rowcount;

    if (@rc <> 1)
      raiserror (N'Expected exactly one post with Id:%i. Found:%i', 
        16, 1 , @postId, @rc);

    insert into Revisions
      (PostId, Text, EditNumber)
    select @postID, @text, EditNumber
    from @nextEditNumber;

    set @revisionId = scope_identity();

    commit;    
  end try
  begin catch
   ... // Error handling omitted
  end catch
end

我省略了错误处理,请参阅模板过程的异常处理和嵌套事务,而不是正确处理错误和嵌套事务。

您会注意到 Posts 表有一个 LastRevision 字段,用作后期修订的增量。这比每次添加修订时计算 MAX 好得多,因为它避免了修订的(范围)扫描。它还起到了并发保护的作用:一次只有一个事务能够更新它,并且只有该事务才能继续插入新的修订。并发事务将阻塞并等待第一个提交,然后下一个未阻塞的事务将正确地将修订号更新为 +1。

于 2010-08-01T00:28:34.267 回答
2

多个用户可以同时编辑同一个帖子吗?如果没有,那么您就没有竞争条件,除非单个用户可以同时提交多个编辑。

于 2010-08-01T00:22:49.893 回答
1

如果只有提交评论的用户才允许修改,那么您可以接受上述内容 - 如果多个用户可以修改单个评论,那么就有问题的余地。

于 2010-08-01T00:23:27.723 回答
1

由于每个 Post 在 Posts 表中只有一条记录,因此请使用锁。

读取 Posts 表中的记录并使用表提示 [WITH (ROWLOCK, XLOCKX)] 获取排他锁。将锁定超时设置为等待几毫秒。

如果进程获得锁,那么它可以添加修订记录。如果进程无法获得锁,则让进程重试。如果进程无法获得锁,则重试几次后,返回错误。

于 2010-08-01T04:16:01.367 回答
0

由于 EditNumber 是由集合中的成员资格确定的属性,因此请让集合提供它。

使 EditNumber 成为计算列 - 具有较小 RevisionID 的同一帖子的记录数。

于 2010-08-01T16:13:39.840 回答