Sql server 为什么SQL Server显式谓词锁定不允许在谓词锁之外插入语句

Sql server 为什么SQL Server显式谓词锁定不允许在谓词锁之外插入语句,sql-server,concurrency,transactions,locking,predicate,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, dep

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

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个
部门
行:

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)
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)
我们还有3个
employee
行:

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)
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锁定属于第一
部门的所有员工:

SELECT * 
FROM employee 
WITH (HOLDLOCK) 
WHERE department_id = 1
现在,同时,预计Bob不能使用相同的部门id插入新的
员工

INSERT INTO employee WITH(NOWAIT) (department_id, name, salary, id) 
VALUES (1, 'Carol', 9000, 6)
上面的insert语句以超出了
锁定请求超时期而结束,这很好,因为Alice锁定了该特定范围

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

INSERT INTO employee WITH(NOWAIT) (department_id, name, salary, id) 
VALUES (3, 'Dave', 9000, 7)
此insert语句使用的
部门id
值超出了Alice谓词锁的范围。但是,这个insert语句也会导致超出了
锁定请求超时期的
异常

为什么SQL Server
HOLDLOCK
谓词锁超出其范围

使现代化 通过向FK添加索引:

create index IDX_DEPARTMENT_ID on employee (department_id)

将第一个和第二个
部门的
employee
条目数增加到1000,我设法看到谓词锁的行为与预期的一样。

满足
SELECT
查询的唯一方法是执行表扫描。没有任何自然资源,它只能根据
部门id
定位和锁定,因此它最终锁定所有行并防止任何插入


在这个玩具示例中,仅仅在
部门id
上添加索引不会有帮助,因为优化器仍然会选择执行表扫描。但是,在一个更大、更现实的表上,我相信添加这样一个索引将允许查询以更有针对性的方式应用锁。

满足
SELECT
查询的唯一方法是执行表扫描。没有任何自然资源,它只能根据
部门id
定位和锁定,因此它最终锁定所有行并防止任何插入


在这个玩具示例中,仅仅在
部门id
上添加索引不会有帮助,因为优化器仍然会选择执行表扫描。然而,在一个更大更现实的表上,我相信添加这样一个索引将允许查询以更具针对性的方式应用锁。

Damien所说的是正确的。当您在部门id(谓词列)上没有索引时,范围会增加,而HoldLock意味着

HOLDLOCK意味着可串行化,因此允许选择但阻止更新和删除T1选择的行,以及T1选择范围内的任何插入

因此,在这种情况下,下表的索引将有所帮助,我的测试也证实了这一点

下面是我做的一个样本测试

在第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
INSERT INTO employee WITH(NOWAIT) (department_id, name, salary, id) 
VALUES (3, 'Dave', 9000, 7)
在第二节:

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
INSERT INTO employee WITH(NOWAIT) (department_id, name, salary, id) 
VALUES (3, 'Dave', 9000, 7)
现在,上面的插入成功了

参考文献:

达米恩说的是正确的。当你在部门id(谓词列)上没有索引时,范围会增加,HoldLock意味着

HOLDLOCK意味着可串行化,因此允许选择但阻止更新和删除T1选择的行,以及T1选择范围内的任何插入

因此,在这种情况下,下表的索引将有所帮助,我的测试也证实了这一点

下面是我做的一个样本测试

在第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
INSERT INTO employee WITH(NOWAIT) (department_id, name, salary, id) 
VALUES (3, 'Dave', 9000, 7)
在第二节:

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
INSERT INTO employee WITH(NOWAIT) (department_id, name, salary, id) 
VALUES (3, 'Dave', 9000, 7)
现在,上面的插入成功了

参考文献: