Тупик с одной хранимой процедурой и несколькими потоками

1

Я выполняю некоторые тесты на транзакции 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.

  • 1
    Классический способ взаимоблокировки: используя «сериализуемый» уровень изоляции, сделайте так, чтобы spid A заблокировал чтение некоторых данных (без updlock ); пусть spid B установит блокировку чтения тех же данных ; Теперь, как spid A берет блокировку записи для тех же данных (пытается изменить столбец), и spid B пытается сделать то же самое. Теперь они оба зашли в тупик друг на друга.
  • 1
    @Marc Марк, я не думаю, что простой SELECT может заблокировать чтение строки, не так ли?
Показать ещё 3 комментария
Теги:
sql-server
stored-procedures
multithreading

2 ответа

0

Чтобы создать тупик, вам обязательно понадобятся две разные процедуры или, по крайней мере, ветки исполнения, которые приобретают блокировки в другом порядке.

Фактически, обеспечение строгого порядка при приобретении замков является широко известным средством предотвращения взаимоблокировок.

Таким образом, вам понадобятся два разных замка 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

Это, в принципе, должно делать трюк на всех уровнях изоляции транзакций. (Вы используете явные блокировки в обоих потоках, поэтому для СУБД было бы смело игнорировать это.) Однако я не совсем уверен, будут ли два последовательных выбора из одной таблицы фактически получить два отдельных блокировки (один на каждой затронутой записи), или если СУБД может просто решить заблокировать всю таблицу или, по крайней мере, один ее диапазон, чтобы эффективно удерживать только одну блокировку. Следовательно, чтобы убедиться, что вы фактически получаете две блокировки в разные моменты времени, вы можете захотеть распространить два выбора на две разные таблицы.

  • 0
    будет ли ваш метод работать на всех уровнях изоляции?
  • 0
    Я думаю, что так и должно быть, если вы используете правильные замки. Предложение TomTom о WITH (UPDLOCK) для явной блокировки записи звучит многообещающе. С другой стороны, если вы хотите создать тупиковую ситуацию с комбинацией блокировок чтения и записи, результат, скорее всего, будет зависеть от уровня изоляции.
Показать ещё 1 комментарий
0

Подумайте о необходимости блокировки обновлений в списке. С (UPDLOCK).

Это гарантирует, что SELECT уже обновит запись.

  • 0
    «Я пытаюсь завести тупик» - это может показаться полной противоположностью полезного с точки зрения достижения этой цели

Ещё вопросы

Сообщество Overcoder
Наверх
Меню