为什么SQL Server显式谓词锁定不允许谓词锁之外的INSERT语句

时间:2017-03-03 13:31:10

标签: sql-server concurrency transactions locking predicate

假设我们有以下数据库表:

create table department (
    id bigint not null, 
    budget bigint not null, 
    name varchar(255), 
    primary key (id)
)

create table employee (
    id bigint not null, 
    name varchar(255), 
    salary bigint not null, 
    department_id bigint, 
    primary key (id)
)

alter table employee 
add constraint FKbejtwvg9bxus2mffsm3swj3u9
foreign key (department_id) references department

我们有3 department行:

insert into department (name, budget, id) 
values ('Department 1', 100000, 1)

insert into department (name, budget, id) 
values ('Department 2', 75000, 2)

insert into department (name, budget, id) 
values ('Department 3', 90000, 3)

我们还有3 employee行:

insert into employee (department_id, name, salary, id) 
values (1, 'CEO', 30000, 1)

insert into employee (department_id, name, salary, id) 
values (1, 'CTO', 30000, 2)

insert into employee (department_id, name, salary, id) 
values (2, 'CEO', 30000, 3)

假设我们有两个并发用户:Alice和Bob。

首先,Alice锁定属于第一个department的所有员工:

SELECT * 
FROM employee 
WITH (HOLDLOCK) 
WHERE department_id = 1

现在,与此同时,预计Bob无法使用相同的employee插入新的department_id

INSERT INTO employee WITH(NOWAIT) (department_id, name, salary, id) 
VALUES (1, 'Carol', 9000, 6)

上面的插入语句以Lock request time out period exceeded结束,这很好,因为Alice锁定了该特定范围。

但是,为什么以下插入也被阻止:

INSERT INTO employee WITH(NOWAIT) (department_id, name, salary, id) 
VALUES (3, 'Dave', 9000, 7)

此insert语句使用的department_id值超出了Alice的谓词锁定范围。但是,这个插入语句最终会出现Lock request time out period exceeded异常。

为什么SQL Server HOLDLOCK谓词锁超出了它的范围?

更新

通过向FK添加索引:

create index IDX_DEPARTMENT_ID on employee (department_id)

并且将第一个和第二个employeedepartment个条目的数量增加到1000个,我设法看到谓词锁的行为符合预期。

2 个答案:

答案 0 :(得分:2)

可以满足SELECT查询的唯一方法是执行表扫描。没有自然资源可以根据department_id定位和锁定,因此最终会锁定所有行并阻止任何插入。

在这个玩具示例中,仅在department_id上添加索引将无济于事,因为优化器仍将选择执行表扫描。但是,在更大的更现实的表上,我认为添加这样的索引将允许查询以更有针对性的方式应用锁。

答案 1 :(得分:1)

Damien所说的是正确的..当你没有部门ID(谓词列)的索引时,范围增加而HoldLock意味着

  

HOLDLOCK表示SERALIZABLE,因此允许SELECTS,但阻止T1选择的行的UPDATE和DELETES,以及T1选择范围内的任何INSERT。

所以在这种情况下,下面表格的索引会有所帮助,我的测试确认了相同的

以下是我做的样本测试

会话1中的

create  index nci_department_id on dbo.employee(department_id)
include
(id,name,salary)
go

begin tran

SELECT * 
FROM employee 
WITH (HOLDLOCK) 
WHERE department_id = 1
会话2中的

INSERT INTO employee WITH(NOWAIT) (department_id, name, salary, id) 
VALUES (3, 'Dave', 9000, 7)

现在上面的插入成功了

<强>参考文献:
https://stackoverflow.com/a/7845331/2975396