Nun*_*iro 5 sql-server locking
我需要开发一个服务器应用程序(在C#中),它将从一个简单的表中读取行(在SQL Server 2005或2008中),做一些工作,比如调用Web服务,然后用结果状态更新行(成功,错误).
看起来很简单,但是当我添加以下应用程序必需条件时,事情变得更加困难:
对于负载平衡和容错目的,必须同时运行多个应用程序实例.通常,应用程序将部署在两个或更多服务器上,并将同时访问同一个数据库表.每个表行只能处理一次,因此必须在多个应用程序实例之间使用通用的同步/锁定机制.
当应用程序实例处理一组行时,其他应用程序实例不必等待它结束,以便读取等待处理的不同行集.
如果应用程序实例崩溃,则不需要对正在处理的表行进行手动干预(例如,删除用于在崩溃实例正在处理的行上进行应用程序锁定的临时状态).
应该以类似队列的方式处理行,即应该首先处理最旧的行.
虽然这些要求看起来并不太复杂,但我在提出解决方案时遇到了一些麻烦.
我见过锁定提示的建议,如XLOCK
,UPDLOCK
,ROWLOCK
,READPAST
,等,但我看不出有任何锁提示,让我实现这些必要条件的组合.
谢谢你的帮助.
问候,
Nuno Guerreiro
这是一个典型的表作为队列模式,如将表用作队列中所述。您将使用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调用之前崩溃的情况。
您无法使用 SQL 事务(或此处依赖事务作为主要组件)来执行此操作。事实上,你可以这样做,但你不应该这样做。对于长锁来说,事务不应该以这种方式使用,并且您不应该像这样滥用它们。
保持事务打开那么长时间(检索行、调用 Web 服务、返回进行一些更新)根本不好。并且没有任何optimistic locking
隔离级别可以让您做您想做的事。
使用 ROWLOCK 也不是一个好主意,因为它就是这样。一个提示。它会受到锁升级的影响,并且可以转换为表锁。
我可以建议您的数据库使用单一入口点吗?我认为它适合酒吧/订阅设计。因此,只有一个组件可以读取/更新这些记录:
in progress
或类似的东西。