Я выполняю некоторые тесты на транзакции SQL в многопоточной среде. Я пытаюсь создать тупик, выполнив одну хранимую процедуру в цикле из двух потоков, работающих в паралели. Оба моих потока используют один и тот же метод при запуске, который непрерывно выполняет одну хранимую процедуру:
using (TestDataContext db = new TestDataContext())
{
while (true)
{
db.DeadLocking();
}
}
Может ли кто-нибудь привести пример хранимой процедуры "DeadLocking", которая надежно создавала бы тупик в этом случае senario. Он должен использовать транзакции (одно или несколько). Я довольно много исследовал и видел много примеров того, как создавать взаимоблокировки в sql, однако ни один из них не работал в моем коде. Пожалуйста помоги.
Обновление: после предложений Marc я пробовал этот sproc безрезультатно:
CREATE PROCEDURE [dbo].[DeadLocking]
AS
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
SET NOCOUNT ON
BEGIN TRANSACTION
DECLARE @val varchar(1)
SELECT @val = Record FROM Test.dbo.Records WHERE RecordId = 1
UPDATE Test.dbo.Records SET Record = @val WHERE RecordId = 1
COMMIT TRANSACTION
END
Запуск его из обоих потоков в паралеле, который должен блокировать эти потоки друг на друга. Что я делаю не так?
Обновление: описанная выше процедура вызывает тупик, однако для этого требуется не менее 3 потоков, чтобы сделать это 2 (не знаю, почему, может быть, это занимает 2, а также длится вечно). Смешно, что это также вызывает тупик:
CREATE PROCEDURE [dbo].[DeadLocking]
AS
BEGIN
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
UPDATE Test.dbo.Records SET Record = 1 WHERE RecordId = 1
END
Я предполагаю, что это происходит, потому что сама хранимая процедура реализует какую-то логику транзакций за сценой. Если у кого-то есть больше информации о том, почему это происходит, поделитесь. Имейте в виду, что тупик происходит только в UPDATE и не происходит в SELECT. Это происходит на уровнях изоляции SERIALIZABLE и REPEATABLE READ.
Чтобы создать тупик, вам обязательно понадобятся две разные процедуры или, по крайней мере, ветки исполнения, которые приобретают блокировки в другом порядке.
Фактически, обеспечение строгого порядка при приобретении замков является широко известным средством предотвращения взаимоблокировок.
Таким образом, вам понадобятся два разных замка A и B, например, на двух разных таблицах. Затем один поток попытается заблокировать A, затем B, в то время как другой поток попытается заблокировать B тогда A. Только таким образом появляется шанс создать тупик.
Если вы хотите увеличить вероятность возникновения тупиковой ситуации, которая действительно возникает во время выполнения, потребуется некоторая задержка, например:
lock A
delay5Seconds
lock B
delay5Seconds
unlock B
unlock A
а также
lock B
delay5Seconds
lock A
delay5Seconds
unlock A
unlock B
Это позволяет другому потоку попасть в нужный момент времени до тупика.
Чтобы это детерминистически порождало тупик каждый раз, нужно было бы создать механизм для синхронизации обоих исполнений потоков, например
lock A
wait for thread #2 to lock B
lock B
...
а также
lock B
wait for thread #1 to lock A
lock A
...
РЕДАКТИРОВАТЬ:
Нашел этот SO-вопрос, который, как представляется, связан: Confused about UPDLOCK, HOLDLOCK
Экстраполируя это, вы можете попробовать:
BEGIN TRANSACTION
SELECT * FROM Test.dbo.Records WHERE RecordId = 1 WITH (UPDLOCK, HOLDLOCK)
WAITFOR DELAY '00:00:10'
SELECT * FROM Test.dbo.Records WHERE RecordId = 999999 WITH (UPDLOCK, HOLDLOCK)
WAITFOR DELAY '00:00:10'
COMMIT TRANSACTION
а также
BEGIN TRANSACTION
SELECT * FROM Test.dbo.Records WHERE RecordId = 999999 WITH (UPDLOCK, HOLDLOCK)
WAITFOR DELAY '00:00:10'
SELECT * FROM Test.dbo.Records WHERE RecordId = 1 WITH (UPDLOCK, HOLDLOCK)
WAITFOR DELAY '00:00:10'
COMMIT TRANSACTION
Это, в принципе, должно делать трюк на всех уровнях изоляции транзакций. (Вы используете явные блокировки в обоих потоках, поэтому для СУБД было бы смело игнорировать это.) Однако я не совсем уверен, будут ли два последовательных выбора из одной таблицы фактически получить два отдельных блокировки (один на каждой затронутой записи), или если СУБД может просто решить заблокировать всю таблицу или, по крайней мере, один ее диапазон, чтобы эффективно удерживать только одну блокировку. Следовательно, чтобы убедиться, что вы фактически получаете две блокировки в разные моменты времени, вы можете захотеть распространить два выбора на две разные таблицы.
WITH (UPDLOCK)
для явной блокировки записи звучит многообещающе. С другой стороны, если вы хотите создать тупиковую ситуацию с комбинацией блокировок чтения и записи, результат, скорее всего, будет зависеть от уровня изоляции.
Подумайте о необходимости блокировки обновлений в списке. С (UPDLOCK).
Это гарантирует, что SELECT уже обновит запись.
updlock
); пусть spid B установит блокировку чтения тех же данных ; Теперь, как spid A берет блокировку записи для тех же данных (пытается изменить столбец), и spid B пытается сделать то же самое. Теперь они оба зашли в тупик друг на друга.