应该对SQL Server锁定体系结构进行哪些更改以使其更适合开发人员?

时间:2010-02-26 18:45:44

标签: sql-server locking rowlocking

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

我已经阅读了有关SQL锁定升级的内容,并且我已经看过在锁定提示中使用特定索引,但是你知道,当表中有大量索引存在数百万条记录和并发更新时,这是不切实际的需要在这些记录上发生。对于小型表和特定查询,可以获得所需的行为,但是当表具有较大的宽度(许多列)并且有许多进程使用数据时,此方法会熔化并成为真正的争用点。

我希望添加的是新的lockhint ,因为 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

2 个答案:

答案 0 :(得分:3)

您之前已经问过这个问题并得到答案:fix your schema and your code。在该帖子中,锁定冲突是IX锁定,意图锁定冲突表示高粒度锁定,这反过来指示表扫描。你不需要锁定提示,你只需要一个索引和体面的查询。以你的另一个问题Why does row level locking not appear to work correctly in 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';

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

作为旁注,此处描述的锁已经存在并被称为键锁。它们是SQL Server运行的默认隐式模式。您认为SQL Server在世界上如何发布每秒16000 tpc事务的TPC-C基准数?您拥有服务器所需的所有并行功能,只需阅读一两本书即可了解如何使用它。有很多关于这个主题的文献,你可以从Transaction Processing: Concepts and Techniques开始。

<强>更新

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 Clause中的队列段落。在队列中,您有一个要处理的资源表,每个线程抓取一个,锁定它并开始处理:

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.*;   

你会看到每个会话抓住它自己的资源,锁定它并跳过其他人锁定的所有内容。事实上,我在生产中拥有一个完全像这样运行的系统,在表格中有超过5M的资源(它们是Web服务支付处理请求),并且从100个concurent处理器中出发和处理大约每秒50个(大约需要2秒。每次调用过程)。在一块垃圾硬件上。所以绝对有可能。

答案 1 :(得分:0)