Может ли произойти взаимоблокировка с тем же методом доступа? - PullRequest
4 голосов
/ 09 февраля 2012

Возможно ли возникновение взаимоблокировки, если два одновременных оператора DML изменяют одни и те же данные и используют один и тот же метод доступа?

Исходя из моих тестов и моих предположений о том, как работает Oracle, ответ - нет.

Но я хочу быть на 100% уверенным. Я ищу официальный источник, который говорит, что взаимоблокировки не могут происходить таким образом, или тестовый пример, демонстрирующий, что взаимоблокировки могут возникать таким образом.


Другой способ задать этот вопрос: будет ли Oracle всегда возвращать результаты в том же порядке, если используется один и тот же метод доступа? (И никаких изменений данных между прогонами.)

Например, если запрос использует полное сканирование таблицы и возвращает строки в порядке 4/3/2/1, будет ли всегда возвращать строки в этом порядке? И если сканирование диапазона индекса возвращает строки в порядке 1/2/3/4, будет ли всегда возвращать строки в этом порядке? Неважно, каков фактический порядок, просто он является детерминированным.

(Параллелизм может добавить некоторую сложность этому вопросу. Общий порядок выражения будет различным в зависимости от многих факторов. Но для блокировки я считаю, что важен только порядок в каждом параллельном сеансе. И снова мое тестирование показывает, что порядок является детерминированным и не приведет к тупику.)


UPDATE

Мой оригинальный вопрос был немного общим. Больше всего меня интересует, можно ли запустить что-то вроде update table_without_index set a = -1 в двух разных сеансах одновременно и получить взаимоблокировку? (Я спрашиваю об одном обновлении, а не о серии обновлений.)

Во-первых, позвольте мне продемонстрировать, что одно и то же утверждение может вызвать тупик.

Создать таблицу, индекс и некоторые данные:

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

Обратите внимание, что я создаю таблицу с pctfree 0, и обновленные значения будут занимать значительно больше места, поэтому будет много миграций строк. (Это ответ на ответ @Tony Andrew, хотя я боюсь, что мой тест может быть слишком упрощенным. Кроме того, я не думаю, что нам нужно беспокоиться о вставке строк между обновлениями; только одно из обновлений увидит новую строку, поэтому это не приведет к тупику. Если новый ряд не переместит также кучу других вещей.)

drop table deadlock_test purge;

create table deadlock_test(a number) pctfree 0;
create index deadlock_test_index on deadlock_test(a);

insert into deadlock_test select 2 from dual connect by level <= 10000;
insert into deadlock_test select 1 from dual connect by level <= 10000;

commit;

Запустить этот блок в сеансе 1:

begin
    while true loop
        update deadlock_test set a = -99999999999999999999 where a > 0;
        rollback;
    end loop;
end;
/

Запустить этот блок в сеансе 2:

--First, influence the optimizer so it will choose an index range scan.
--This is not gaurenteed to work for every environment.  You may need to 
--change other settings for Oracle to choose the index over the table scan.
alter session set optimizer_index_cost_adj = 1;

begin
    while true loop
        update deadlock_test set a = -99999999999999999999 where a > 0;
        rollback;
    end loop;
end;
/

Через несколько секунд одна из этих сессий выдает ORA-00060: deadlock detected while waiting for resource. Это связано с тем, что один и тот же запрос блокирует строки в разном порядке в каждом сеансе.

За исключением описанного выше сценария, может ли произойти тупик?

Выше показано, что изменение плана выполнения может привести к тупику. Но может ли возникнуть тупик, даже если план выполнения останется прежним?

Насколько я могу судить, если вы удалите optimizer_index_cost_adj или что-либо еще, что изменит план, код никогда не вызовет тупиковую ситуацию. (Я выполняю код некоторое время без ошибок.)

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

Может ли кто-нибудь создать тестовый сценарий, в котором один оператор обновления, выполняющийся одновременно и использующий один и тот же план, вызывает тупик?

Ответы [ 2 ]

4 голосов
/ 09 февраля 2012

«Порядок» является детерминированным с вашей точки зрения , только если вы включите ORDER BY в свой запрос. Является ли он детерминированным с точки зрения сервера - это деталь реализации, на которую нельзя полагаться.

Что касается блокировки, два идентичных оператора DML могут блокировать (но не блокировать) друг друга. Например:

CREATE TABLE THE_TABLE (
    ID INT PRIMARY KEY
);

Транзакция A:

INSERT INTO THE_TABLE VALUES(1);

Транзакция B:

INSERT INTO THE_TABLE VALUES(1);

В этот момент транзакция B останавливается на до тех пор, пока транзакция A не завершится или не откатится назад. Если A фиксирует, B терпит неудачу из-за нарушения PRIMARY KEY. Если А откатывается назад, В добивается успеха.

Подобные примеры могут быть построены для ОБНОВЛЕНИЯ и УДАЛЕНИЯ.

Важным моментом является то, что блокировка не будет зависеть от плана выполнения - независимо от того, как Oracle решит оптимизировать ваш запрос, у вас всегда будет одинаковое поведение блокировки. Вы можете прочитать о Автоматических блокировках в операциях DML для получения дополнительной информации.


Что касается мертвых -блокировок, их можно достичь с помощью нескольких операторов. Например:

A: INSERT INTO THE_TABLE VALUES(1);
B: INSERT INTO THE_TABLE VALUES(2);
A: INSERT INTO THE_TABLE VALUES(2);
B: INSERT INTO THE_TABLE VALUES(1); -- SQL Error: ORA-00060: deadlock detected while waiting for resource

Или, возможно, с операторами, которые изменяют более одной строки в другом порядке и с некоторыми очень неудачными сроками (кто-нибудь может это подтвердить?).

--- ОБНОВЛЕНИЕ ---

В ответ на обновление вашего вопроса позвольте мне сделать общее замечание: если параллельные потоки выполнения блокируют объекты в согласованном порядке , взаимоблокировки невозможны. Это верно для любого типа блокировки, будь то мьютексы в вашей обычной многопоточной программе (например, см. мысли Херба Саттера о блокировках иерархии ) или базы данных. Как только вы меняете порядок таким образом, что любые две блокировки «переворачиваются», появляется вероятность возникновения взаимных блокировок.

Не сканируя индекс, вы обновляете ( и блокируете ) строки в одном порядке, а индекс - в другом. Итак, это, вероятно, то, что происходит в вашем случае:

  • Если вы отключите сканирование индекса для обеих одновременных транзакций , они обе заблокируют строки в одном и том же порядке [X], поэтому тупик невозможен.
  • Если вы включите сканирование индекса только для одной транзакции , они больше не будут блокировать строки в том же порядке, что может привести к тупику.
  • Если вы включили сканирование индекса для обеих транзакций , то снова обе они блокируют строки в одном и том же порядке, и невозможна взаимоблокировка (попробуйте alter session set optimizer_index_cost_adj = 1; в обеих сессиях, и вы ' посмотрим).

[X] Хотя я не стал бы полагаться на сканирование полной таблицы с гарантированным порядком - это может быть просто из-за того, как работает Oracle в этих конкретных обстоятельствах, и некоторые будущие Oracle или другие обстоятельства могут привести к другому поведению. 1062 *

Таким образом, наличие индекса является случайным - реальная проблема заключается в порядке. Так уж получилось, что на порядок в UPDATE может влиять индекс, но если бы мы могли повлиять на порядок другим способом, мы бы получили аналогичные результаты.

Поскольку UPDATE не имеет ORDER BY, вы не можете гарантировать порядок блокировки только с помощью UPDATE. Однако, если вы отделяете блокировку от обновления, то вы можете гарантировать порядок блокировки:

SELECT ... ORDER BY ... FOR UPDATE;

Хотя ваш исходный код вызывал взаимоблокировки в моей среде Oracle 10, следующий код этого не делает:

Сессия 1:

declare
    cursor cur is select * from deadlock_test where a > 0 order by a for update;
begin
    while true loop
        for locked_row in cur loop
            update deadlock_test set a = -99999999999999999999 where current of cur;
        end loop;
        rollback;
    end loop;
end;
/

Сессия 2:

alter session set optimizer_index_cost_adj = 1;

declare
    cursor cur is select * from deadlock_test where a > 0 order by a for update;
begin
    while true loop
        for locked_row in cur loop
            update deadlock_test set a = -99999999999999999999 where current of cur;
        end loop;
        rollback;
    end loop;
end;
/
1 голос
/ 09 февраля 2012

Порядок, в котором возвращаются строки, не является детерминированным. После обновления строка может «мигрировать» в другой блок, и в этом случае она будет отображаться в другом положении в результатах полного сканирования таблицы. Или (возможно, более вероятно) новая строка может быть вставлена ​​между двумя существующими строками.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...