我应该使用什么SQL Server 2005/2008锁定方法来处理多个服务器应用程序实例中的单个表行?

Nun*_*iro 5 sql-server locking

我需要开发一个服务器应用程序(在C#中),它将从一个简单的表中读取行(在SQL Server 2005或2008中),做一些工作,比如调用Web服务,然后用结果状态更新行(成功,错误).

看起来很简单,但是当我添加以下应用程序必需条件时,事情变得更加困难:

  • 对于负载平衡和容错目的,必须同时运行多个应用程序实例.通常,应用程序将部署在两个或更多服务器上,并将同时访问同一个数据库表.每个表行只能处理一次,因此必须在多个应用程序实例之间使用通用的同步/锁定机制.

  • 当应用程序实例处理一组行时,其他应用程序实例不必等待它结束,以便读取等待处理的不同行集.

  • 如果应用程序实例崩溃,则不需要对正在处理的表行进行手动干预(例如,删除用于在崩溃实例正在处理的行上进行应用程序锁定的临时状态).

  • 应该以类似队列的方式处理行,即应该首先处理最旧的行.

虽然这些要求看起来并不太复杂,但我在提出解决方案时遇到了一些麻烦.

我见过锁定提示的建议,如XLOCK,UPDLOCK,ROWLOCK,READPAST,等,但我看不出有任何锁提示,让我实现这些必要条件的组合.

谢谢你的帮助.

问候,

Nuno Guerreiro

Rem*_*anu 5

这是一个典型的表作为队列模式,如将表用作队列中所述。您将使用Pending Queue,出队事务也应安排合理的超时重试。实际上在网络通话期间无法保持锁定。成功后,您将删除待处理的项目。

您还需要能够分批出队,如果您承受大量负载(每秒100和数千次操作),那么逐个出队就太慢了。因此,以链接的文章中的“待处理队列”示例为例:

create table PendingQueue (
  id int not null,
  DueTime datetime not null,
  Payload varbinary(max),
  cnstraint pk_pending_id nonclustered primary key(id));

create clustered index cdxPendingQueue on PendingQueue (DueTime);
go

create procedure usp_enqueuePending
  @dueTime datetime,
  @payload varbinary(max)
as
  set nocount on;
  insert into PendingQueue (DueTime, Payload)
    values (@dueTime, @payload);
go

create procedure usp_dequeuePending
  @batchsize int = 100,
  @retryseconds int = 600
as
  set nocount on;
  declare @now datetime;
  set @now = getutcdate();
  with cte as (
    select top(@batchsize) 
      id,
      DueTime,
      Payload
    from PendingQueue with (rowlock, readpast)
    where DueTime < @now
    order by DueTime)
  update cte
    set DueTime = dateadd(seconds, @retryseconds, DueTime)
    output deleted.Payload, deleted.id;
go
Run Code Online (Sandbox Code Playgroud)

成功处理后,您将使用ID从队列中删除项目。如果出现故障或崩溃,它将在10分钟内自动重试。有人认为您必须内部化,因为只要HTTP不提供事务语义,您就永远无法使用100%一致的语义来做到这一点(例如,保证没有任何项目被处理两次)。您可以实现很高的错误率,但是总有一段时间,在更新数据库之前,HTTP调用成功之后,系统可能会崩溃,并且由于无法区分这种情况而导致重试该项目。系统 HTTP调用之前崩溃的情况。


Mar*_* N. 2

您无法使用 SQL 事务(或此处依赖事务作为主要组件)来执行此操作。事实上,你可以这样做,但你不应该这样做。对于长锁来说,事务不应该以这种方式使用,并且您不应该像这样滥用它们。

保持事务打开那么长时间(检索行、调用 Web 服务、返回进行一些更新)根本不好。并且没有任何optimistic locking隔离级别可以让您做您想做的事。

使用 ROWLOCK 也不是一个好主意,因为它就是这样。一个提示。它会受到锁升级的影响,并且可以转换为表锁。

我可以建议您的数据库使用单一入口点吗?我认为它适合酒吧/订阅设计。因此,只有一个组件可以读取/更新这些记录:

  1. 读取批量消息(足以供所有其他实例使用)- 1000、10000,无论您认为合适。它通过某种排队方式使这些批次可供其他(并发)组件使用。我不会说 MSMQ :)(这将是我今天第二次推荐它,但它也非常适合您的情况)。
  2. 它将消息标记为in progress或类似的东西。
  3. 您的消费者都在事务上绑定到入站队列并做他们的事情。
  4. 准备好后,在 Web 服务调用之后,他们将消息放入出站队列中。
  5. 中央组件拾取它们,并在分布式事务内对数据库进行更新(如果失败,消息将保留在队列中)。由于它是唯一可以执行该操作的操作,因此您不会遇到任何并发问题。至少数据库上没有。
  6. 同时它可以读取下一个待处理的批次,依此类推。