Тупик, вызванный оператором SELECT JOIN с SQL Server

При выполнении оператора SELECT с СОЕДИНЕНИЕМ двух таблиц SQL Server, по-видимому, блокирует обе таблицы оператора по отдельности. Например, по такому запросу:

SELECT ...
FROM
    table1
    LEFT JOIN table2
        ON table1.id = table2.id
    WHERE ...

Я узнал, что порядок блокировок зависит от условия WHERE. Оптимизатор запросов пытается создать план выполнения, который считывает ровно столько строк, сколько необходимо. Таким образом, если условие WHERE содержит столбец table1, оно сначала получит строки результата из table1, а затем получит соответствующие строки из table2. Если столбец из таблицы2, он сделает это наоборот. Более сложные условия или использование индексов также могут повлиять на решение оптимизатора запросов.

Когда данные, считанные оператором, должны быть обновлены позже в транзакции с операторами UPDATE, не гарантируется, что порядок операторов UPDATE соответствует порядку, который использовался для чтения данных из 2 таблиц. Если другая транзакция пытается прочитать данные, пока транзакция обновляет таблицы, это может вызвать взаимоблокировку, когда оператор SELECT выполняется между операторами UPDATE, потому что ни SELECT не может получить блокировку для первой таблицы, ни UPDATE не может получить блокировку для второй стол. Например:

T1: SELECT ... FROM ... JOIN ...
T1: UPDATE table1 SET ... WHERE id = ?
T2: SELECT ... FROM ... JOIN ... (locks table2, then blocked by lock on table1)
T1: UPDATE table2 SET ... WHERE id = ?

Обе таблицы представляют иерархию типов и всегда загружаются вместе. Поэтому имеет смысл загружать объект с помощью SELECT с JOIN. Загрузка обеих таблиц по отдельности не даст оптимизатору запросов возможности найти наилучший план выполнения. Но поскольку операторы UPDATE могут обновлять только одну таблицу за раз, это может привести к взаимоблокировкам, когда объект загружается, когда объект обновляется другой транзакцией. Обновления объектов часто вызывают UPDATE в обеих таблицах, когда обновляются свойства объекта, принадлежащие к разным типам иерархии типов.

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

Единственный обходной путь, поэтому пока кажется, что чтение может вообще не блокироваться. В SQL Server 2005 это можно сделать с помощью SNAPSHOT ISOLATION. Единственным способом для SQL Server 2000 было бы использование уровня изоляции READ UNCOMMITED.

Я хотел бы знать, есть ли другая возможность предотвратить эти взаимоблокировки со стороны SQL Server?


person Reboot    schedule 14.09.2010    source источник
comment
Если это вопрос, то ответ - нет. Это происходит со всеми уровнями изоляции, за исключением, возможно, READ UNCOMMITTED, который я не тестировал, потому что я не хочу, чтобы транзакции могли считывать наполовину обновленные данные.   -  person Reboot    schedule 15.09.2010


Ответы (3)


Этого никогда не произойдет при изоляции моментальных снимков, когда читатели не блокируют писатели. Кроме этого, нет никакого способа предотвратить такие вещи. Здесь я написал множество сценариев воспроизведения: "nofollow noreferrer">Воспроизведение взаимоблокировок только с одной таблицей

Редактировать:

У меня нет доступа к SQL 2000, но я бы попытался сериализовать доступ к объекту с помощью sp_getapplock, чтобы чтение и изменение никогда не выполнялись одновременно. Если вы не можете использовать sp_getapplock, разверните собственный мьютекс.

person A-K    schedule 14.09.2010
comment
Ответ помог мне найти решение с SQL Server 2005 или новее. Но программное обеспечение по-прежнему используется и с SQL Server 2000, я, вероятно, могу реализовать разные решения для обеих версий, поэтому решение, которое работает для всех версий, или другое решение для SQL Server 2000 будет оценено по достоинству. - person Reboot; 17.09.2010

Другой способ исправить это — разделить выбор... из... соединения на несколько операторов выбора. Установите уровень изоляции для чтения зафиксированных. Используйте табличную переменную для передачи данных из select для соединения с другими. Используйте Different для фильтрации вставок в эти табличные переменные.

Итак, если у меня есть две таблицы A, B. Я вставляю/обновляю в A, а затем в B. Где, поскольку оптимизатор запросов sql предпочитает сначала читать B, а A. Я разделю один выбор на 2 выбора. Сначала я прочитаю B. Затем передам эти данные следующему оператору выбора, который читает A.

Здесь взаимоблокировка не произойдет, потому что блокировки чтения в таблице B будут сняты, как только будет выполнено 1-е выражение.

PS Я столкнулся с этой проблемой, и это сработало очень хорошо. Гораздо лучше, чем мой ответ на силовой приказ.

person Ankush    schedule 24.06.2011

Я столкнулся с той же проблемой. Использование подсказки запроса FORCE ORDER решит эту проблему. Недостатком является то, что вы не сможете использовать лучший план, который есть у оптимизатора запросов для вашего запроса, но это предотвратит взаимоблокировку.

Итак (это от пользователя «Bill the Lizard»), если у вас есть запрос FROM table1 LEFT JOIN table2, а ваше предложение WHERE содержит только столбцы из table2, план выполнения обычно сначала выбирает строки из table2, а затем ищет строки из table1 . При небольшом результирующем наборе из таблицы 2 необходимо получить только несколько строк из таблицы 1. С FORCE ORDER сначала должны быть извлечены все строки из таблицы 1, поскольку в ней нет предложения WHERE, затем строки из таблицы 2 объединяются, а результат фильтруется с использованием предложения WHERE. Тем самым ухудшая производительность.

Но если вы знаете, что это не так, используйте это. Возможно, вы захотите оптимизировать запрос вручную.

Синтаксис

SELECT ...
FROM
    table1
    LEFT JOIN table2
        ON table1.id = table2.id
    WHERE ...
OPTION (FORCE ORDER)
person Ankush    schedule 21.04.2011
comment
На самом деле я написал комментарий о производительности, кажется, Stack Overflow перепутал ваш ответ и мой комментарий, я уже задавался вопросом, почему он исчез. Проблема не только в производительности FORCE ORDER, но и в блокировке. Если вы используете FORCE ORDER и это приводит к тому, что план выполнения считывает все строки из table1, он также накладывает на них общую блокировку. Таким образом, в основном каждый запрос, который использует только столбцы из таблицы 2, блокирует всю таблицу 1, что делает невозможным любые обновления. Это не отдельный запрос, который должен работать, SELECT должен работать с любым предложением WHERE. - person Reboot; 22.04.2011
comment
Итак, вы не хотите блокировать таблицу 1, но при этом иметь возможность присоединиться к таблице 2? В моем случае вставки происходили в таблице 1, а затем в таблице 2. И оптимизатор запросов выбирал обратный порядок в случае соединения. Так что тупик. Добавление FORCE ORDER решило, что, следовательно, нет тупиковой ситуации. Обратите внимание, что мне все еще нужна блокировка чтения для таблицы 1. Вставки могут дождаться завершения выбора. Но никакого тупика не будет. - person Ankush; 22.04.2011
comment
Никакие блокировки в таблице 1 недопустимы, но с FORCE ORDER блокируются все строки в таблице, а не только строки, которые необходимо соединить со строками из таблицы 2. Выделение блокировок для всех строк также может стать проблемой, потому что это ресурсы, которые должны выделять процессы. Если SQL Server использует нашу память для блокировок, процессы могут блокироваться при выделении памяти. - person Reboot; 23.04.2011