为什么 SQL Server 使用不同的键范围锁定 SELECT? [英] Why does SQL Server lock SELECT with a different key range?

查看:59
本文介绍了为什么 SQL Server 使用不同的键范围锁定 SELECT?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两个事务一个接一个地开始:

I have two transactions that start one after another:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

BEGIN TRANSACTION
select * from MyTable WITH (XLOCK) WHERE Id = 1
WAITFOR DELAY '00:00:10';
COMMIT TRANSACTION

第二个几乎一样(只是没有延迟和另一个id)

Second one is almost the same (just no delay and another id)

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

BEGIN TRANSACTION
select * from MyTable WITH (XLOCK) WHERE Id = 2
COMMIT TRANSACTION

我确实有一个关于 Id 的非唯一索引.至少当 Id 上有主键时,这似乎确实按预期工作.

I do have a non-unique index on Id. It does seem that this work as expected when there is a primary key on Id at least.

据我所知,第一个事务应该真正获得Id = 1的key-range锁,而另一个应该获得id 2的key-range锁.

From what I understand the first transaction should really obtain a key-range lock for the Id = 1, while the other should obtain a key-range lock for id 2.

显然它不会像那样工作,因为在第一个完成之前,第二个事务被卡住了.我在这里遗漏了什么或者我不能强制独占键范围锁定?

Apparently it does not work like that since the transaction two is stuck until the first one completes. Do I miss something here or I cannot force an exclusive key-range lock?

这是我的示例的完整创建脚本:

Here is the full create script for my example:

CREATE TABLE [dbo].[MyTable](
    [Id] [bigint] NOT NULL,
 CONSTRAINT [PK_MyTable] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

推荐答案

据我了解,第一笔交易应该真正获得Id = 1 的键范围锁,而另一个应该获得一个id 2 的键范围锁.

From what I understand the first transaction should really obtain a key-range lock for the Id = 1, while the other should obtain a key-range lock for id 2.

无论哪种方式都采用键范围锁定,范围具有下限和上限,索引类型(唯一或非唯一)定义了两个边界的值/限制:

A key-range lock is taken either way, a range has lower&upper boundaries and the type of index (unique or non-unique) defines the values/limits for the two boundaries:

假设查询索引的现有值(如问题中的 id=1 & id=2),当索引为:

Assuming, a query for existing values of the index (like in the question, id=1 & id=2), when index is:

唯一:下键范围边界 = 上键范围边界 = 索引/行值

Unique : lower key-range boundary = upper key-range boundary = index/row value

非唯一:范围下限=索引/行值&范围上限=索引的下一个值(如果有).

Non-unique : lower range boundary = index/row value & upper range boundary= next value of index (if any).

当 index 是唯一的时,第一个查询选择 Id=1,这将独占(XLOCK)从 index_value = 1 到 index_value=1 的键范围.第二个查询,对于Id=2,可以选择行,因为Id=2没有被锁定.

When index is unique, the first query selects Id=1 and this locks exclusively (XLOCK) the key-range from index_value = 1 to index_value=1. The second query, for Id=2, can select the row, because Id=2 is not locked.

当 index 不唯一时,第一个查询 (Id=1) 只锁定从 index_value = 1 到 index_value=2 的键范围.第二个查询(对于 Id=2)无法选择行,因为 Id=2 被第一个查询(独占)锁定.

When index is non-unique, the first query (Id=1) locks exclusively the key range from index_value = 1 to index_value=2. The second query (for Id=2) can not select the row since Id=2 is locked (exclusively) by the first query.

如果表有第三行,值为 3..5..10,那么选择这些值中的任何一个的第二个查询就可以正常工作,因为键范围锁是从 Id 1 到 2.

If the table had a third row, with values 3..5..10, then a second query selecting any of these values would work just fine, because the key-range lock is from Id 1 to 2.

create table mytable(Id bigint not null, index idxId /*unique*/ nonclustered (id));
go
insert into mytable(Id) values (1), (2), (5), (7), (20), (21), (22);
go


set transaction isolation level serializable;
begin transaction
select * from mytable with(xlock) where id = 1;

--nonunique index: rangeXX, 
--........locked values: 1&2 for select...Id=1
--........locked values: 2&5 for select...Id=2
select tl.request_mode, tl.request_type, tl.request_status, tl.resource_description, irs.*
from
sys.dm_tran_locks as tl 
left join
(
select %%lockres%% as idxresourcedescription, Id as [column:Id/value]
from mytable with (index(idxId), nolock)
) as irs
on tl.resource_description = irs.idxresourcedescription;

--rollback transaction
go


--in another session/window
select * from mytable with(xlock, serializable) where id = 5; --this is not blocked...
raiserror('', 0, 0) with nowait;
select * from mytable with(xlock, serializable) where id = 2; --...but this is blocked
go

它变得稍微复杂"一些;在尝试选择(独占和可序列化)不存在的值时评估范围锁定(正如您在评论中注意到的,关于 (ffffffffffff) 范围锁定).

It gets slightly more "complicated" to assess the range locks when trying to select (exclusively&serializable) a value that does not exist (as you have noticed, in your comment, about the (ffffffffffff) range lock).

在上面的示例/代码中,选择不存在的 Id=34 将锁定范围 Id= 22 - ∞(数据类型的最大范围).尝试选择 Id = 25 的第二个查询(在另一个会话中)将被阻止(因为它也需要锁定范围 22 - ∞).sys.dm_tran_locks 或 sp_lock 将仅报告键范围的一个值(上限):(ffffffffffff)==∞.下边界(推断?)= max(id).

In the example/code above, selecting Id=34, which does not exist, will lock the range Id= 22 - ∞ (max range of datatype). A second query (in another session) trying to select Id = 25 will be blocked (because it needs to lock the range 22 - ∞ too). sys.dm_tran_locks or sp_lock will report only one value(upper boundary) for the keyrange : (ffffffffffff)==∞. The lower boundary (inferred?) = max(id).

因此,选择 Id=-20 将锁定键范围 [-∞] - 1 并且 sys.dm_tran_locks 报告一个边界(Id=1).下边界(推断)= [-∞].

Accourdingly, selecting Id=-20 will lock the keyrange [-∞] - 1 and sys.dm_tran_locks reports one boundary (Id=1). The lower boundary (inferred) = [-∞].

您可以尝试猜测,当表具有 Id (7)、(20) 并且查询选择 Id=15(具有非唯一和/或唯一索引)时,keyrange 锁定.从概念上讲,在可序列化事务中,有一个键范围锁,需要对现有的键/值进行锁(没有 Id=15 的行,锁必须在不同的键/值上).

You could try and guesstimate, the keyrange lock when table has Ids (7), (20) and a query selects Id=15 (with a nonunique and/or unique index). Conceptually, in serializable transaction there is a keyrange lock and locks need to be taken on existing keys/values (there is no row with Id=15, locks have to be on different keys/values).

这篇关于为什么 SQL Server 使用不同的键范围锁定 SELECT?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆