为什么 SQL 服务器显式谓词锁定不允许在谓词锁定之外插入语句
Why does SQL Server explicit predicate locking disallow INSERT statements outside of the predicate lock
假设我们有以下数据库表:
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 无法使用相同的 department_id
插入新的 employee
:
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)
此插入语句使用了一个 department_id
值,该值超出了 Alice 的谓词锁的范围。但是,此插入语句也以 Lock request time out period exceeded
异常结束。
为什么 SQL 服务器 HOLDLOCK
谓词锁超出其范围?
更新
通过向 FK 添加索引:
create index IDX_DEPARTMENT_ID on employee (department_id)
并将第一个和第二个 department
中的 employee
条目数增加到 1000,我设法看到谓词锁的行为符合预期。
满足 SELECT
查询的唯一方法是执行 table 扫描。没有自然资源可以仅基于 department_id
定位和锁定,因此它最终锁定所有行并阻止任何插入。
在这个玩具示例中,仅在 department_id
上添加索引无济于事,因为优化器仍会选择执行 table 扫描。然而,在更大更现实的 table 上,我相信添加这样的索引将允许查询以更有针对性的方式应用锁。
Damien 说的是正确的..当你在部门 ID(谓词列)上没有索引时,范围会增加并且 HoldLock 意味着
HOLDLOCK means SERALIZABLE and therefore allows SELECTS, but blocks UPDATE and DELETES of the rows selected by T1, as well as any INSERT in the range selected by 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
在会话 2 中:
INSERT INTO employee WITH(NOWAIT) (department_id, name, salary, id)
VALUES (3, 'Dave', 9000, 7)
现在上面的插入成功了
参考文献:
假设我们有以下数据库表:
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 无法使用相同的 department_id
插入新的 employee
:
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)
此插入语句使用了一个 department_id
值,该值超出了 Alice 的谓词锁的范围。但是,此插入语句也以 Lock request time out period exceeded
异常结束。
为什么 SQL 服务器 HOLDLOCK
谓词锁超出其范围?
更新
通过向 FK 添加索引:
create index IDX_DEPARTMENT_ID on employee (department_id)
并将第一个和第二个 department
中的 employee
条目数增加到 1000,我设法看到谓词锁的行为符合预期。
满足 SELECT
查询的唯一方法是执行 table 扫描。没有自然资源可以仅基于 department_id
定位和锁定,因此它最终锁定所有行并阻止任何插入。
在这个玩具示例中,仅在 department_id
上添加索引无济于事,因为优化器仍会选择执行 table 扫描。然而,在更大更现实的 table 上,我相信添加这样的索引将允许查询以更有针对性的方式应用锁。
Damien 说的是正确的..当你在部门 ID(谓词列)上没有索引时,范围会增加并且 HoldLock 意味着
HOLDLOCK means SERALIZABLE and therefore allows SELECTS, but blocks UPDATE and DELETES of the rows selected by T1, as well as any INSERT in the range selected by 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
在会话 2 中:
INSERT INTO employee WITH(NOWAIT) (department_id, name, salary, id)
VALUES (3, 'Dave', 9000, 7)
现在上面的插入成功了
参考文献: