Избегайте эксклюзивных блокировок доступа к ссылочным таблицам при DROPping в PostgreSQL.

Почему удаление таблицы в PostgreSQL требует блокировки ACCESS EXCLUSIVE для любых таблиц, на которые есть ссылки? Как я могу уменьшить это до блокировки ACCESS SHARED или вообще без блокировки? есть ли способ удалить отношение без блокировки таблицы, на которую ссылаются?

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

Чтобы уточнить,

CREATE TABLE base (
    id SERIAL,
    PRIMARY KEY (id)
);
CREATE TABLE main (
    id SERIAL,
    base_id INT,
    PRIMARY KEY (id),
    CONSTRAINT fk_main_base (base_id)
        REFERENCES base (id)
        ON DELETE CASCADE ON UPDATE CASCADE
);
DROP TABLE main; -- why does this need to lock base?

person Dave    schedule 21.08.2015    source источник
comment
Что бы вы хотели, чтобы произошло вместо этого? Вы пытаетесь убрать мебель из оживленного ресторана, люди еще не успели пообедать, за что им платить? половина еды? полноценный обед? Только та часть, которую они потребляли?   -  person wildplasser    schedule 21.08.2015
comment
@wildplasser Я не понимаю твоей аналогии; Я говорю не о блокировках таблицы, которую я удаляю, а о таблице, на которую она ссылается. Таблице, на которую указывает ссылка, не должно быть дела до того, что происходит удаление — ее данные не будут изменены действием. Это вообще не должно ничего делать.   -  person Dave    schedule 21.08.2015
comment
@wildplasser смотрите мой пример   -  person Dave    schedule 21.08.2015
comment
Я понимаю. Мне до сих пор не ясно, почему вы хотите удалить и (возможно) пересоздать таблицы. Кстати: кислота в каталогах жесткая; может быть, postgres слишком оборонительный здесь.   -  person wildplasser    schedule 22.08.2015
comment
@wildplasser таблицы не воссоздаются. Эти таблицы используются для обеспечения изоляции клиентов (например, project_1_data и т. д.). Мы переходим к использованию для этого схем (где мы можем просто дублировать общую информацию), но, поскольку мы все еще находимся в процессе перехода с MySQL, мы пока не можем внести это изменение. Таблицы удаляются, когда мы архивируем мертвые проекты, но, что более важно, во время интеграционных тестов, когда мы быстро создаем и уничтожаем проекты, чтобы обеспечить изоляцию среды их тестирования.   -  person Dave    schedule 22.08.2015
comment
Мне удалось воспроизвести ваш казус. И после некоторого переосмысления я должен сделать вывод, что наблюдаемое поведение является правильным, преднамеренным и необходимым. Если DDL еще не зафиксирован, другой сеанс не должен нарушать ограничения. Как только транзакция зафиксирована, другой сеанс может делать все, что разрешено новой моделью данных. (Я думаю, что использование нескольких версий схемы могло бы избежать необходимости в эксклюзивной блокировке, но это было бы очень сложно сделать правильно)   -  person wildplasser    schedule 22.08.2015
comment
@wildplasser, который объяснил бы, почему ему необходимо блокировать записи в базовой таблице (чтобы другие потоки не могли удалить запись и вызвать сканирование удаляемой таблицы, хотя я ожидаю, что это можно было бы решить достаточно легко, немедленно делает ограничение недействительным?), но я до сих пор не понимаю, зачем нужна полная блокировка ACCESS EXCLUSIVE. Если вы не говорите, что он не различает тип изменения DDL и просто использует логику, которую эта таблица каким-то образом меняет - лучше заблокировать то, к чему она подключена?   -  person Dave    schedule 22.08.2015
comment
Хотя я не могу придумать никаких изменений DDL, которые потребовали бы блокировки ACCESS EXCLUSIVE для таблицы, на которую ссылаются. Какой вред может нанести другой поток, читающий данные из base, в любом случае, если мы никогда не собираемся менять таблицу? Если бы другой поток хотел изменить базовый DDL, ему обязательно потребовалась бы блокировка ACCESS EXCLUSIVE, и если бы текущий поток имел даже блокировку ACCESS SHARED на base, этого было бы достаточно, чтобы заблокировать это. Не могли бы вы уточнить, почему вы считаете, что замок необходим?   -  person Dave    schedule 22.08.2015
comment
Первичный ключ базовой таблицы (обновление, удаление) может каскадироваться в main.base_id. Но он не может каскадироваться, если main отсутствует или изменен. Я опубликую свою тестовую установку в качестве ответа. (удаление каскада, похоже, не меняет поведение; удаление FK делает (очевидно)   -  person wildplasser    schedule 22.08.2015
comment
@wildplasser, но разве ОБЩИЙ ДОСТУП недостаточно, чтобы гарантировать, что ОБНОВЛЕНИЕ и УДАЛЕНИЕ не могут произойти?   -  person Dave    schedule 22.08.2015
comment
Нет, этого недостаточно. План запроса для другого сеанса также может измениться. Поэтому его необходимо признать недействительным. Или предотвратил...   -  person wildplasser    schedule 23.08.2015


Ответы (3)


Для тех, кто ищет в Google и пытается понять, почему их таблица удаления (или удаление внешнего ключа или добавление внешнего ключа) застряла на долгое время:

PostgreSQL (я смотрел версии с 9.4 по 13) ограничения внешнего ключа на самом деле реализуются с помощью триггеров на обоих концах внешнего ключа.

Если у вас есть таблица компании (идентификатор в качестве первичного ключа) и таблица bank_account (идентификатор в качестве первичного ключа, company_id в качестве внешнего ключа, указывающего на company.id), то на самом деле в таблице bank_account есть 2 триггера, а также 2 триггера в компании. стол.

table_name timing trigger_name function_name
bank_account AFTER UPDATE RI_ConstraintTrigger_c_1515961 RI_FKey_check_upd
bank_account AFTER INSERT RI_ConstraintTrigger_c_1515960 RI_FKey_check_ins
company AFTER UPDATE RI_ConstraintTrigger_a_1515959 RI_FKey_noaction_upd
company AFTER DELETE RI_ConstraintTrigger_a_1515958 RI_FKey_noaction_del

Первоначальное создание этих триггеров (при создании внешнего ключа) требует блокировки SHARE ROW EXCLUSIVE для этих таблиц (раньше это была блокировка ACCESS EXCLUSIVE в версии 9.4 и ранее). Эта блокировка не конфликтует с блокировками чтения данных, но будет конфликтовать со всеми другими блокировками, например, простой INSERT/UPDATE/DELETE в таблицу компании.

Для удаления этих триггеров (при удалении внешнего ключа или всей таблицы) требуется блокировка ACCESS EXCLUSIVE для этих таблиц. Этот замок конфликтует с любым другим замком!

Итак, представьте себе сценарий, в котором у вас есть работающая транзакция A, которая сначала выполнила простой SELECT из таблицы компании (заставив ее удерживать блокировку ACCESS SHARE для таблицы компании до тех пор, пока транзакция не будет зафиксирована или не будет отменена), а теперь выполняет какую-то другую работу для 3 минуты. Вы пытаетесь удалить таблицу bank_account в транзакции B. Для этого требуется блокировка ACCESS EXCLUSIVE, которая должна будет подождать, пока сначала не будет снята блокировка ACCESS SHARE. Кроме того, все другие транзакции, которые хотят получить доступ к таблице компаний (просто SELECT или, возможно, INSERT/UPDATE/DELETE), будут поставлены в очередь для ожидания блокировки ACCESS EXCLUSIVE, которая ожидает блокировки ACCESS SHARE.

Длительные транзакции и изменения DDL требуют деликатной обработки.

person zutnop    schedule 21.01.2021
comment
Пожалуйста, не добавляйте один и тот же ответ на несколько вопросов. Ответьте на лучший и пометьте остальные как дубликаты. См. раздел Можно ли добавлять повторяющиеся ответы на несколько вопросов? - person Paul Roub; 21.01.2021
comment
Это не позволит мне пометить другой как дубликат, потому что здесь еще нет принятого ответа. Но я удалил свой ответ из другого вопроса. - person zutnop; 21.01.2021
comment
теперь на него есть принятый ответ ;) — это отличная информация; спасибо за ответ на вопрос 6-летней давности! - person Dave; 22.01.2021

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


Чтобы избежать взаимоблокировки, вы можете использовать рекомендательную блокировку:

start transaction;
select pg_advisory_xact_lock(0);
drop table main;
commit;

Это гарантирует, что только один клиент одновременно запускает DDL с использованием ссылочных таблиц, поэтому не имеет значения, в каком порядке будут получены другие блокировки.


Вы можете избежать блокировки таблицы в течение длительного времени, сначала удалив внешний ключ:

start transaction;
select pg_advisory_xact_lock(0);
alter table main drop constraint fk_main_base;
commit;
start transaction;
drop table main;
commit;

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

person Tometzky    schedule 22.08.2015
comment
Насколько короче? Есть ли ограничение на X секунд? Или просто на 10% короче? Я спрашиваю, потому что знание того, сколько времени имеет значение между возможностью делать это онлайн с большой базой и нет. - person Alan; 13.09.2017

person    schedule
comment
При откате другая сессия потерпит неудачу: я думаю, что это возможность, которая была очевидна для вас, но я почему-то проигнорировал: транзакция DROP TABLE может откатиться. Так что это объясняет, почему ограничение нельзя сразу отбросить и забыть. Я все еще не уверен в типе блокировки; ALTER TABLE и UPDATE являются хорошими примерами того, где необходима блокировка, но я считаю, что обоих можно избежать с помощью блокировки ACCESS SHARED. Есть ли пример, который вы можете придумать, где ACCESS SHARED будет недостаточно, или это просто postgres, который слишком защищается, как вы изначально предполагали? - person Dave; 22.08.2015
comment
Эмпирическое правило: не смешивайте DDL и DML. (IOW: отключите другие сеансы, когда вы выполняете DDL. В целях тестирования это не вызовет проблем) И да: этот тип блокировки курса слишком консервативен; с причиной. - person wildplasser; 23.08.2015