Что ограничивает область действия ограничения CHECK при проверке межстрочного правила?

Что ограничивает область действия ограничения CHECK при проверке межстрочного правила?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

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

Исторический контекст

Реляционная модель разделяет ограничения домена и ограничения целостности отношения. SQL получил механизм CHECK прежде всего для проверки допустимости значений отдельного кортежа: диапазонов, обязательных зависимостей между столбцами и других локальных условий.

Для межстрочных правил стандарт SQL предусматривает более общую идею утверждений, или ASSERTION. Однако этот механизм практически не поддерживается большинством распространённых СУБД, поэтому на практике применяют ключи, внешние ключи, специальные ограничения, триггеры или изменение структуры данных.

Постановка проблемы

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

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

Подробное решение

Ограничение CHECK оценивается в контексте проверяемой строки при её вставке или изменении. Оно не является общим запросом к таблице и не предназначено для подсчёта других строк или проверки отсутствия совпадений во всём отношении.

Для локального правила вроде «дата окончания не раньше даты начала» CHECK является подходящим инструментом. Для межстрочного правила нужно выбрать механизм, который действительно видит согласованное состояние нескольких строк:

  • PRIMARY KEY или UNIQUE выражают запрет дубликатов по ключу;
  • FOREIGN KEY контролирует существование связанной строки;
  • отдельная таблица с правильно выбранным ключом иногда превращает межстрочное правило в обычную уникальность;
  • триггер может выполнить сложную проверку, но требует аккуратного учёта блокировок и изоляции транзакций;
  • ASSERTION, если СУБД его поддерживает, предназначен для общих условий целостности.

Важно различать проверку и гарантию. Запрос из приложения перед вставкой может обнаружить нарушение в текущем снимке данных, но без подходящей блокировки или ограничения две конкурентные транзакции способны получить одинаковый результат проверки. Надёжное правило должно контролироваться на уровне базы данных с учётом конкурентного доступа.

Ситуация из практики

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

Вариант с CHECK не подходит: пересечение зависит от других строк и интервалов. Вариант с триггером выражает правило, но усложняет блокировки и тестирование; при ошибочной изоляции гонка всё ещё возможна. Вариант с отдельными дискретными слотами позволяет задать уникальность по ресурсу и слоту, но требует изменения модели и подходит только для дискретного времени.

Если бизнес допускает дискретные слоты, предпочтителен последний вариант: межстрочное правило преобразуется в ограничение уникальности, которое СУБД проверяет атомарно. Для произвольных интервалов выбирают поддерживаемый СУБД механизм исключающих ограничений либо тщательно реализованный триггер с корректной синхронизацией.

Что кандидаты часто упускают

  1. Может ли CHECK содержать запрос к той же таблице для поиска конфликтующей строки?

В стандартной модели SQL CHECK не является механизмом для произвольного подзапроса по таблице. Даже если конкретная СУБД допускает нестандартное расширение, такое решение может иметь ограничения по моменту проверки, конкурентному доступу и переносимости. Для общего межстрочного утверждения следует использовать подходящий объект целостности, а не маскировать его под CHECK.

  1. Почему проверка в приложении перед INSERT не гарантирует межстрочную целостность?

Потому что проверка и запись могут выполняться конкурентно. Две транзакции могут одновременно увидеть, что конфликтующей строки нет, после чего обе вставят свои строки. Гарантию даёт атомарное ограничение базы данных или явно согласованная схема блокировок и транзакций, а не сам факт предварительного SELECT.

  1. Всегда ли межстрочное правило требует триггера?

Нет. Сначала следует проверить, можно ли выразить его ключом или внешним ключом. Например, правило «у каждого клиента только одна текущая запись» иногда моделируют отдельной таблицей текущих состояний с уникальным идентификатором клиента. Триггер нужен, когда правило нельзя свести к поддерживаемому ограничению, но он сложнее для сопровождения и требует анализа транзакционной конкуренции.