Недооценённая фича SQLite, о которой мало кто знает
11 июля 2026 года разработчик Эван Хан опубликовал разбор функции SQLite, которая существует с 2021 года, но по-прежнему используется реже, чем могла бы, — strict-таблицы. Тема прямо касается тех, кто администрирует self-hosted инфраструктуру: SQLite давно перестал быть "игрушечной" базой для мобильных приложений — на нём построены метаданные Garage (разбирали в статье про S3-хранилища), конфиги множества self-hosted сервисов, и просто удобная встраиваемая БД для небольших проектов на своём VPS.
В чём проблема обычных таблиц SQLite
По умолчанию SQLite использует "гибкую" типизацию — колонка с типом INTEGER спокойно примет туда текстовую строку:
CREATE TABLE people_nonstrict (age INTEGER);
INSERT INTO people_nonstrict (age) VALUES ('garbage');
-- сохранится без единой ошибки
Для тех, кто привык к строгой типизации PostgreSQL или MySQL, это неприятный сюрприз: ошибка, которая должна была бы упасть на этапе записи, всплывает позже — где-нибудь в коде, который ожидал число, а получил строку.
Похожая история с созданием самой таблицы — SQLite без возражений создаст колонку с несуществующим типом:
CREATE TABLE tbl (name GARBAGE);
CREATE TABLE tbl (name DATETIME);
CREATE TABLE tbl (name UUID);
-- всё это отработает без ошибок, хотя ни один из этих типов SQLite не поддерживает
Часть таких случаев — банальные опечатки, часть — путаница с типами, которых в SQLite попросту нет (своего DATETIME или JSON-типа в SQLite нет, даты и JSON хранятся как текст или blob).
Что даёт STRICT
Достаточно дописать одно слово при создании таблицы:
CREATE TABLE people (name TEXT) STRICT;
После этого:
- Вставка неверного типа падает с ошибкой —
age INTEGER, попытка записать туда текст, вернётerror: cannot store TEXT value in INTEGER column, а не тихо сохранится - Бессмысленные типы колонок при создании таблицы тоже отклоняются —
GARBAGE,UUID,BLOBB(опечатка) и подобное вызовут ошибку уже на этапеCREATE TABLE - Разрешены только реальные типы SQLite:
INT,INTEGER,REAL,TEXT,BLOB,ANY - Колонка обязана иметь тип —
CREATE TABLE tbl (name)без указания типа в strict-таблице не сработает
Отдельно стоит ANY — если для конкретной колонки гибкость всё же нужна, ANY разрешает в неё что угодно, даже в строгой таблице. Полезно для action-логов или key-value-подобных колонок внутри в целом строгой схемы.
Важный нюанс: если значение можно без потерь привести к нужному типу, strict-таблица его пропустит — строка '123' спокойно ляжет в INTEGER-колонку, потому что конвертируется однозначно. Ошибка возникает только там, где преобразование действительно теряет данные или бессмысленно.
Минусы, которые стоит знать заранее
Нельзя "превратить" существующую таблицу в strict через ALTER. Придётся создавать новую таблицу с STRICT, копировать данные и переименовывать:
CREATE TABLE new_people (name TEXT) STRICT;
INSERT INTO new_people SELECT * FROM people;
DROP TABLE people;
ALTER TABLE new_people RENAME TO people;
Если в старых данных уже есть "мусорные" значения (текст там, где ожидалось число), миграция упадёт с ошибкой — придётся сначала почистить данные или явно привести типы через CAST.
Сами разработчики SQLite с этим подходом не вполне согласны. У проекта есть отдельная страница документации, где объясняются практические сценарии, где гибкая типизация — это фича, а не баг: чистое key-value хранилище, таблица с разнородными атрибутами, или прямой импорт "грязного" CSV, где терять данные из-за несовпадения типа не хочется.
Работает только с SQLite 3.37.0 (ноябрь 2021) и новее. Более старая версия SQLite не то что не поддерживает strict-таблицы — она вообще не сможет прочитать файл базы, где такая таблица есть, даже если пытается читать другие, non-strict таблицы из того же файла.
Производительность — теоретически проверка типов при каждой вставке должна чуть замедлять запись, но на практике разница не обнаруживается даже при вставке миллионов строк в широкую таблицу.
Практическая рекомендация для self-hosted проектов
Если администрируешь сервисы на SQLite (или сам пишешь что-то небольшое поверх него) — практичный подход такой:
- Проверить версию SQLite перед началом:
sqlite3 --version— нужна 3.37.0+ - Для новых таблиц в новых проектах — сразу писать
STRICT, это ничего не стоит и отсекает целый класс багов на старте - Для существующих БД — не переписывать всё разом, а мигрировать таблицы на
STRICTпо мере рефакторинга, начиная с самых критичных (там, где ошибка типа дороже всего обходится) - Не пытаться силой сделать строгими key-value-подобные таблицы — для них гибкая типизация или
ANY-колонки действительно уместнее
Для Garage, который использует SQLite для метаданных, это, к слову, забота самого проекта, а не администратора — трогать внутреннюю схему Garage не нужно. Но если рядом крутится собственный сервис на SQLite (небольшая CRM, бот, внутренний трекер задач) — decision "делать таблицы strict с первого дня" стоит принять осознанно, а не по умолчанию.