Объясните, как отложенная проверка внешнего ключа позволяет сохранить взаимозависимые записи в одной транзакции.
Отложенная проверка внешнего ключа переносит контроль ссылочной целостности с момента выполнения отдельного оператора на конец транзакции. Это позволяет сначала создать взаимозависимые строки, а затем завершить их связи до фиксации транзакции.
Если к моменту COMMIT хотя бы одна ссылка остаётся нарушенной, вся транзакция откатывается. Ограничение не отключается, а лишь меняется момент его проверки.
Обычная проверка внешнего ключа выполняется сразу после оператора изменения данных. Такой режим хорошо защищает таблицы от временно некорректного состояния, но не позволяет простыми средствами вставить набор строк с циклическими зависимостями.
Отложенные ограничения нужны для ситуаций, где корректность важна на границе транзакции, а не после каждого промежуточного шага. Подход поддерживает атомарное изменение взаимосвязанных данных без временного отключения контроля целостности.
Предположим, у каждой организации обязательно должен быть основной адрес, а каждый адрес обязан принадлежать организации. При этом организация хранит ссылку на основной адрес, а адрес — ссылку на организацию.
Немедленная проверка создаёт замкнутый порядок: организацию нельзя вставить без адреса, а адрес нельзя вставить без организации. Если сделать один внешний ключ необязательным или временно отключить ограничения, можно получить неполные данные, которые приложение или другой процесс не исправит.
Внешний ключ объявляют откладываемым и проверяемым в конце транзакции. Внутри транзакции допускаются временно нарушенные ссылки, но перед фиксацией СУБД проверяет весь набор изменений.
В реальной схеме взаимные внешние ключи обычно создают после создания обеих таблиц, поскольку при объявлении таблиц одна из них может ещё не существовать. После этого в одной транзакции вставляют обе строки, задавая взаимные идентификаторы, и выполняют фиксацию.
DEFERRABLE означает, что режим проверки можно отложить, а INITIALLY DEFERRED задаёт такое поведение по умолчанию для каждой транзакции. Альтернативный режим INITIALLY IMMEDIATE проверяет ограничение сразу, но допускает явное переключение на отложенную проверку внутри транзакции, если конкретная СУБД это поддерживает.
Компромисс состоит в том, что ошибка обнаруживается позже: не на проблемном операторе, а при проверке ограничения или COMMIT. Поэтому транзакции должны быть короткими, а код обязан корректно обрабатывать ошибку фиксации. Поддержка откладываемых ограничений и точный синтаксис зависят от СУБД; переносимый SQL-код не должен предполагать их наличие без проверки документации.
Отложенная проверка не решает проблему конкурирующих транзакций сама по себе и не заменяет другие ограничения. Для полного инварианта могут потребоваться NOT NULL, UNIQUE, проверки диапазонов и подходящий уровень изоляции.
При миграции старой системы обнаружили две таблицы: профиль хранит обязательную ссылку на текущий документ, а документ — обязательную ссылку на профиль. Нужно было перенести существующие пары в новую схему без периода, когда пользователи видят неполные данные.
Рассматривались три варианта. Временное отключение внешних ключей было простым, но опасным: ошибка миграционного скрипта могла оставить постоянные «висячие» ссылки. Разрешить NULL и выполнить второй проход можно было технически, но это ослабляло модель и требовало дополнительной очистки.
Выбрали откладываемые внешние ключи и одну транзакцию на согласованный пакет данных. Сначала создавались обе строки, затем устанавливались ссылки, после чего выполнялся COMMIT; некорректные пары отклонялись целиком. Это сохранило обязательность связей и обеспечило атомарность миграции.
Чем отложенная проверка отличается от отключения внешнего ключа?
При отложенной проверке ограничение продолжает действовать и обязательно проверяется перед фиксацией транзакции. При отключении контроля СУБД может принять некорректные данные, а последующая проверка либо не выполняется, либо требует отдельной процедуры.
Поэтому отложенный режим безопаснее: он разрешает только промежуточное нарушение, необходимое для текущей транзакции, но не допускает фиксацию неконсистентного результата.
Что произойдёт, если одна из строк останется без требуемой связанной строки к моменту фиксации?
Проверка завершится ошибкой, и транзакция не будет зафиксирована. Все её изменения должны быть отклонены или транзакция должна быть исправлена и повторно завершена.
Важно, что ошибка может возникнуть в COMMIT, а не в операторе вставки. Поэтому обработчик транзакции должен считать фиксацию отдельной потенциальной точкой отказа.
Когда отложенное ограничение не является лучшим решением?
Если циклическая зависимость не отражает настоящую бизнес-модель, лучше изменить схему: сделать одну связь производной, вынести её в отдельную таблицу или выбрать единственного владельца связи. Отложенное ограничение не устраняет избыточность и может усложнить диагностику ошибок.
Его оправданно применять, когда взаимная зависимость действительно обязательна или когда согласованное изменение набора строк невозможно выразить через последовательность операций с немедленной проверкой. Даже тогда нужно учитывать поддержку конкретной СУБД, длительность транзакций и поведение при конкурентных изменениях.