Ограничения, транзакции и безопасный откат
Проверяем отказ неверных изменений и доказываем сохранность снимка после ROLLBACK.
- Проверять CHECK, NOT NULL, UNIQUE и FOREIGN KEY по отдельности.
- Отличать откат одного шага от отката всей транзакции.
- Подтверждать отсутствие побочных изменений точным снимком.
Запись требует отдельной модели риска
В предыдущих уроках все запросы читали данные. UPDATE, INSERT и DELETE меняют состояние, поэтому для практики нужна отдельная локальная копия. Не переносите учебные команды в рабочую БД. Скрипт проверки создаёт соединение :memory:, загружает fixture и закрывает его в конце. Это делает область изменения явной и позволяет повторить опыт с чистого состояния.
Договор схемы ограничивает title длиной от одного до восьмидесяти символов, state одним из трёх значений, priority диапазоном от одного до трёх, estimate неотрицательным значением или NULL. Объявление INTEGER в обычной SQLite-таблице само по себе не является строгим запретом всех значений другого типа; в этом курсе мы проверяем только явно названные целочисленные случаи. Если нужна гарантия типа, её следует отдельно определить и выразить в схеме. Не обещайте тестом больше, чем реально задано ограничениями.
Проверяем одно нарушение за раз
Смена assignee_id на 'missing' должна нарушить внешний ключ при включённом PRAGMA foreign_keys=ON. Смена state на 'deleted' нарушит CHECK, estimate=-1 — другой CHECK, title=NULL — NOT NULL, title='' — проверку длины, повтор users.id='u1' — уникальность ключа. У каждого случая исходная запись должна быть допустимой, а остальные поля не должны создавать конкурирующее нарушение. Иначе ошибка есть, но неизвестно, какой именно инвариант сработал.
Отказ команды не означает автоматического отката всей ранее начатой транзакции. Политика ошибки и тип ограничения важны, поэтому в упражнении каждый негативный случай имеет собственный SAVEPOINT. Сразу после отказа сравнивается полный логический снимок всех таблиц с исходным. После этого выполняются ROLLBACK TO и RELEASE, затем снимок сверяется повторно. Проверка только одной колонки t2 могла бы пропустить лишнюю строку в users. Текст конкретного сообщения не делаем договором: нас интересуют класс отказа и сохранность состояния.
Откат всей операции
PRAGMA foreign_keys=ON;
BEGIN;
UPDATE tasks SET title='Temporary' WHERE id='t2';
SELECT id,title FROM tasks WHERE id='t2';
-- t2 | Temporary
ROLLBACK;
SELECT id,title FROM tasks WHERE id='t2';
-- t2 | DraftSavepoint — управляемый промежуточный шаг
Во втором примере внешняя транзакция меняет estimate у t2 на 8. Затем savepoint отмечает начало следующего шага: priority становится 3. ROLLBACK TO second_step возвращает приоритет 1, но оставляет оценку 8, потому что первое изменение было сделано раньше точки сохранения. RELEASE убирает точку сохранения; внешний ROLLBACK в конце возвращает также estimate=NULL. Вложенный BEGIN для этой задачи не нужен.
При чтении результатов подпишите момент наблюдения. Строки «t2,8,1» и «t2,NULL,1» обе правильные, но относятся к разным фазам. Снимок после завершения должен точно совпасть с начальным. Не объявляйте проверенной устойчивость к аварии процесса или конкурирующим писателям: в нашем опыте одно соединение и управляемый откат. Для проверки конкуренции нужна отдельная постановка с несколькими соединениями, точками синхронизации и ожидаемой политикой блокировок.
Ограничения помогают защищать данные, но не заменяют валидацию продукта. Пробельный title проходит проверку длины базы; бизнес-правило публикации, если оно нужно, должно быть задано отдельно. Значение estimate=NULL разрешено именно потому, что отсутствие оценки допустимо. Негативный набор обязан включать и разрешённые соседние случаи, чтобы не принять чрезмерно строгий запрет за корректное исправление.
Источники и проверка фактов
SQLite · CREATE TABLE constraintsCHECK и NOT NULL — разные ограничения.SQLite · TransactionsГраницы BEGIN, COMMIT, ROLLBACK.SQLite · SavepointsВозврат к промежуточному шагу.SQLite · Foreign key supportВключение внешних ключей на соединении.Авторский учебный материал · Редакция QA Orbit · версия 2026-10-28.1