Почему вставка строки с внешним ключом, ссылающимся на строку с помощью pk, модифицированного в другой транзакции изоляции моментального снимка, приводит к зависанию транзакции? - PullRequest
0 голосов
/ 09 ноября 2018

Я столкнулся с интересной проблемой в системе, где из-за изменения схемы первая транзакция базы данных в одном потоке блокирует выполнение второй транзакции базы данных до истечения времени ожидания.

Чтобы проверить это, я создал тестовую базу данных:

CREATE DATABASE StackOverflow
GO

USE StackOverflow

ALTER DATABASE StackOverflow SET ALLOW_SNAPSHOT_ISOLATION ON
ALTER DATABASE StackOverflow SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE
GO

CREATE TABLE One (
    Id int CONSTRAINT pkOne PRIMARY KEY,
    A varchar(10) NOT NULL
)

CREATE TABLE Two (
    Id int CONSTRAINT pkTwo PRIMARY KEY,
    B varchar(10) NOT NULL,
    OneId int NOT NULL CONSTRAINT fkTwoToOne REFERENCES One
)
GO

-----------------------------------------------

CREATE TABLE Three (
    Id int CONSTRAINT pkThree PRIMARY KEY,
    SurrogateId int NOT NULL CONSTRAINT ThreeSurrUnique UNIQUE,
    C varchar(10) NOT NULL
)
GO

CREATE TABLE Four (
    Id int CONSTRAINT pkFour PRIMARY KEY,
    D varchar(10) NOT NULL,
    ThreeSurrogateId int NOT NULL CONSTRAINT fkFourToThree REFERENCES Three(SurrogateId)
)
GO

--Seed data
INSERT INTO One (Id, A) VALUES (1, '')
INSERT INTO Three (Id, SurrogateId, C) VALUES (3, 50, '')

В этом первом тесте транзакция, изменяющая строку в таблице Один, запускается, но еще не зафиксирована. Другая транзакция вставляется в таблицу Two, причем столбец ссылается на ту же строку, которая была изменена в первой транзакции в таблице One. Вторая транзакция будет зависать вечно, пока первая транзакция не будет зафиксирована.

SQL Management Studio testing transaction hang

Причина, по которой транзакция ожидает, связана с блокировкой ключа LCK_M_S, удерживаемой первой транзакцией.

SQL Management Studio Activity Monitor LCK_M_S keylock

Во втором тесте транзакция, изменяющая строку в таблице 3, запускается, но еще не зафиксирована, как в первом тесте. Другая транзакция вставляется в таблицу Four, причем столбец ссылается на ту же строку, которая была изменена в первой транзакции в таблице Three. За исключением этого времени, таблица четыре ссылается на суррогатный ключ в таблице три вместо первичного ключа. Транзакция завершена немедленно и не зависит от первой транзакции.

SQL Management Studio testing transaction hang without issue

Мне нужна помощь в понимании того, почему последняя транзакция всегда блокируется предыдущей транзакцией при вставке строки в отдельную таблицу, которая ссылается на таблицу, которая была изменена в первой транзакции. Я думаю, что очевидный бесполезный ответ - из-за ограничения внешнего ключа. Но почему? Особенно потому, что это изоляция моментальных снимков, почему последняя транзакция вообще заботится о первой? Строка, на которую она ссылается, уже существует, и внешний ключ может быть легко проверен, что подтверждается вторым тестом, когда внешний ключ, ссылающийся на суррогатный ключ, завершается без препятствий.

1 Ответ

0 голосов
/ 09 ноября 2018

Ответ довольно прост.

Когда запрос читает для проверки ограничений внешнего ключа, они всегда используют блокировки, а не управление версиями строк. Изображение, если одна транзакция изменяет значение PK, и параллельный сеанс вставил строку, ссылающуюся на значение old PK. Не разрешается проверять ограничение FK на основе непротиворечивой версии строки в хранилище версий. Если бы это было так, тогда все FK должны были бы быть проверены снова , когда было принято изменение PK.

В первом случае транзакция обновления имеет блокировку ключа для целевого индекса FK, поэтому одновременный сеанс не может прочитать значения PK.

Во-вторых, обновление не затрагивает уникальный ключ, включенный в FK. Обновление может установить общую блокировку для значения целевого ключа, поскольку сеанс обновления имеет эксклюзивную блокировку ключа для ключа с другим уникальным индексом.

В первом примере после первой транзакции происходит сбой второй с конфликтом обновления изоляции моментального снимка:

Сообщение 3960, Уровень 16, Состояние 2, Строка 10, транзакция изоляции моментального снимка прервано из-за конфликта обновлений. Вы не можете использовать изоляцию снимка для получить доступ к таблице 'dbo.One' прямо или косвенно в базе данных «StackOverflow», чтобы обновить, удалить или вставить строку, которая была изменен или удален другой транзакцией. Повторите транзакцию или изменить уровень изоляции для оператора update / delete.

Это потому, что в изоляции SNAPSHOT вы не можете прочитать строку, которая изменилась с момента запуска вашей транзакции. А так как проверка FK не может использовать версии строк, ей нужно прочитать PK из строки, которая была обновлена ​​ после ее транзакции начата. Это нарушение изоляции SNAPSHOT, поскольку значение PK могло не существовать в начале транзакции SNAPSHOT.

Это может быть немного сложно увидеть, так как транзакция SNAPSHOT на самом деле не запускается в тот момент времени, когда вы запускаете BEGIN TRANSACTION (что-то вроде IMPLICIT TRANSACTIONS) в соответствующей точке. время - это время, когда транзакция впервые читает или изменяет базу данных. EG

if @@trancount > 0 rollback
go
set transaction isolation level snapshot
begin transaction

drop table if exists t
create table t(id int)

--in another session run
--update one set a = a+'b' where id = 1

waitfor delay '0:0:10'

insert into two(id,b,oneid) values (2,'',1) -- fails
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...