1

我最近遇到了一个相当令人沮丧的情况,当select * from table with (rowlock updlock) where key=value针对它执行这样的语句时,SQL 服务器拒绝只针对主键发出锁。现在不要误会我的意思,它确实锁定了行,但它更进一步并锁定了表。

我已经阅读了有关 SQL 锁升级的信息,并且我已经研究过在锁提示中使用特定索引,但是你看,当一个表中有数以百万计的记录和并发更新需要大量索引时,这是不切实际的发生在那些记录上。对于一个小表和一个特定的查询,可以获得所需的行为,但是当表的宽度很大(很多列)并且有很多进程使用数据时,这种方法就会失效,并可能成为真正的争论点。

我希望看到添加的是一个的锁定提示,因为PKLock(代表主键锁定)会针对行的主键发出锁定,并且任何时候使用索引、表扫描或其他方法来获取该行,它将检查此锁并兑现它,而不是锁定整个表。

因此不需要发出这样的表锁,这将大大增加针对数据库并行执行代码的能力。

请权衡这个想法并指出它可能存在的任何缺陷,可以改进的方法或应该添加的其他元素以解决我的困境。

编辑

@Remus

如果我执行这个查询

begin transaction
select lockname from locks  where lockname='A'
update Locks Set locked=1 where lockname='A'

然后这个查询:

begin transaction
select lockname from locks  where lockname='A'

在提交事务之前,在两个示例中都返回 A 行。这是更新后面的读数,而不是阻塞。

一个成功的解决方案应该在不指定要使用的索引的情况下执行以下操作:

  1. 使用查询 1:读取并锁定 A,更新 A
  2. 使用查询 2:读取并锁定 B,更新 B,提交查询 2
  3. 使用查询 2:读取 B 并被阻止,直到 A 上的锁被释放
  4. 使用查询 1:提交查询 1
  5. 使用查询 2:读取并锁定 A,更新 A,提交查询 2
4

2 回答 2

3

您之前曾问过这个问题并得到了答案:修复您的架构和代码。在那篇文章中,锁冲突是 IX 锁,意图锁的冲突表示高粒度锁,这反过来又表示表扫描。你不需要锁定提示,你只需要一个索引和体面的查询。以您的另一个问题为例,为什么行级锁定在 SQL Server 中似乎无法正常工作?答案很简单:锁表需要由 LockName 上的聚集索引来组织:

CREATE TABLE [dbo].[Locks]( 
    [LockName] [varchar](50) NOT NULL, 
    [Locked] [bit] NOT NULL, 
    CONSTRAINT [PK_Locks] PRIMARY KEY CLUSTERED  ([LockName]));
GO    

insert into Locks (LockName, Locked) values ('A', 0);
insert into Locks (LockName, Locked) values ('B', 0);
GO

在一个会话中执行以下操作:

begin transaction
update Locks 
  set Locked=1 
  output inserted.*
  where LockName = 'A';

在另一个会话中这样做:

begin transaction
update Locks 
  set Locked=1 
  output inserted.*
  where LockName = 'B';

没有更新冲突,没有阻塞,不需要(错误的)提示,什么都没有。只是好的 ole' 正确的架构和查询设计。

作为旁注,您在此处描述的锁已经存在,称为钥匙锁。它们是 SQL Server 运行的默认隐式模式。您如何想象 SQL Server 可以发布每秒 16000 tpc 事务的 TPC-C 基准数?您拥有服务器中所需的所有并行能力,您只需要阅读一两本书即可了解如何使用它。关于这个主题有很多文献,您可以从事务处理:概念和技术开始。

更新

begin transaction 
select lockname from locks  where lockname='A' 
update Locks Set locked=1 where lockname='A'

无论您尝试多少/不同的锁定提示,这永远不会奏效。这就是您使用输出语法进行更新的原因:

begin transaction 
update Locks 
 Set locked=1 
 output inserted.*
 where lockname='A'

这确保您首先更新,然后返回您更新的内容。这种技术在数据库中非常常见,正是您所寻求的语义:资源获取。事实上,这种技术是资源获取典型代表的基石:队列处理。请参阅OUTPUT 子句中的队列段落。在队列中,您有一张要处理的资源表,每个线程抓取一个,锁定它并开始处理:

create table Resources (
   id int identity(1,1) not null,
   enqueue_time datetime not null default getutcdate(),
   is_processing bit not null default 0,
   payload xml);

create clustered index cdxResources on Resources 
  (is_processing, enqueue_time);
go   

-- enqueue:
insert into Resources (payload) values ('<do>This</do>');
insert into Resources (payload) values ('<do>That</do>');
insert into Resources (payload) values ('<do>Something</do>');
insert into Resources (payload) values ('<do>Anything</do>');

现在从单独的会话中运行:

--dequeue
begin transaction;
with cte as (
  select top(1) *
  from Resources with(readpast)
  where is_processing = 0
  order by enqueue_time)
update cte
  set is_processing = 1
  output inserted.*;   

您会看到每个会话都获取它自己的资源,将其锁定并跳过其他人锁定的所有内容。碰巧我在生产中拥有一个运行完全一样的系统,表中有超过 500 万个资源(它们是 Web 服务支付处理请求),并且从 100 个并发处理器(大约需要 2 秒。每个呼叫处理)。在一块垃圾硬件上。所以绝对有可能。

于 2010-02-26T21:21:36.083 回答
0

也许您根本不想指定任何锁定?

http://blogs.msdn.com/davidlean/archive/2009/04/06/sql-server-nolock-hint-other-poor-ideas.aspx

于 2010-02-26T19:19:12.603 回答