Почему эта комбинация внешнего + первичного ключа вызывает Postgres тупик? - PullRequest
1 голос
/ 11 апреля 2020

Начните с этих двух таблиц и начальных записей для c:

create table c
(
    id   serial primary key,
    name varchar not null
);

create table e
(
    id   varchar                  not null,
    c_id bigint references c (id) not null,
    name varchar                  not null,
    primary key (id, c_id)
);

insert into c (name) values ('deadlock test');

Тема 1:

begin;
select * from c where id = 1 for update;
insert into e (id, c_id, name) VALUES ('bar', 1, 'second') on conflict do nothing ;
commit;

Тема 2:

begin;
insert into e (id, c_id, name) VALUES ('bar', 1, 'first') on conflict do nothing ;
commit;

Порядок выполнения:

  • Тема 1: начало
  • Тема 2: начало
  • Тема 1: блокировка c
  • Тема 2: вставить e
  • Тема 1: вставить e <- тупик </li>

Почему это происходит?

Добавление блокировки для c в потоке 2, конечно, можно избежать тупика, но мне не понятно почему. Также интересно то, что если строка в e существует до запуска потока 1 или 2., тогда не возникает тупиков.

Я подозреваю, что происходит как минимум две вещи:

  • Первичный ключ создает уникальное ограничение, которое вызывает некоторую блокировку на e, которую я не понимаю, даже с ON CONFLICT DO NOTHING.
  • Внешний ключ на c_id приводит к некоторому триггеру вызывая блокировку c при вставке новой записи (или когда c_id обновляется, я полагаю).

Спасибо!

1 Ответ

1 голос
/ 11 апреля 2020

Для обеспечения целостности каждая вставка на e будет блокировать указанную строку в c с блокировкой KEY SHARE. Это предотвращает любую параллельную транзакцию от удаления строки в c или изменения первичного ключа.

Такой KEY SHARE конфликт конфликтов с UPDATE сеансом блокировки 1 явно потребовался (см. документацию ), поэтому INSERT блоков сеанса 2 - но он уже вставил (и заблокировал) индексный кортеж в индекс первичного ключа e.

Теперь сеанс 1 хочет вставить строку с тот же первичный ключ, который вставлен в сеансе 2, поэтому он будет блокировать блокировку, только что принятую сеансом 2, и взаимоблокировка будет идеальной.

Вы, наверное, удивляетесь, почему ON CONFLICT DO NOTHING не меняет поведение. Но PostgreSQL не доходит, потому что, чтобы узнать, есть ли конфликт, сеанс 1 должен будет подождать, пока не узнает, зафиксирован ли сеанс 2 или выполнен ли откат. Таким образом, тупик случается до того, как мы узнаем, будет ли конфликт или нет.

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