我最近遇到了一个相当令人沮丧的情况,即当针对它执行类似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.这是在更新后面阅读,而不是阻止。
成功的解决方案应该在不指定要使用的索引的情况下执行以下操作:
答案 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)