SQLite часто воспринимают как игрушку для прототипов, хотя это самая распространённая СУБД в мире: она работает в браузерах, телефонах, самолётах и на миллионах серверов. Вопрос не в том, «настоящая» ли это база, а в том, подходит ли модель «файл плюс библиотека» вашей нагрузке. Разберём, когда одного файла достаточно, а когда пора ставить PostgreSQL.

Что такое SQLite и чего в нём нет

SQLite — библиотека на C, которая хранит всю базу в одном файле. Нет серверного процесса, сетевого протокола, ролей и сетевой аутентификации. При этом транзакции соответствуют ACID, есть WAL, внешние ключи, JSON-функции, полнотекстовый поиск FTS5 и строгие таблицы STRICT. Формат файла стабилен и переносим между версиями и платформами.

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

Когда файл лучше сервера

Файл выигрывает, когда база живёт рядом с приложением: десктопная программа, мобильное приложение, CLI-утилита, кэш метаданных, тестовые фикстуры, небольшая внутренняя панель. Чтение при этом часто интенсивнее записи, а количество одновременных писателей невелико.

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

Когда нужен сервер

PostgreSQL или MariaDB стоит выбирать, если несколько процессов на разных машинах активно пишут одновременно, нужны роли и гранулярные права, потоковая репликация, автоматический failover, расширения вроде PostGIS или сложные аналитические запросы по большим объёмам. Сервер также упрощает централизованный бэкап и аудит.

При этом «клиент-сервер» не означает автоматически высокую скорость: на одном узле с интенсивным чтением SQLite нередко обгоняет сетевую СУБД за счёт отсутствия передачи данных по сети.

Настройки, которые решают

По умолчанию SQLite осторожна и не слишком быстра. Для серверного файла включите WAL: читатели перестанут блокировать писателя, а писатель — читателей. Режим сохраняется в файле, поэтому достаточно выполнить его один раз.

sqlite3 app.db "PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL; PRAGMA busy_timeout=5000;"
sqlite3 app.db "PRAGMA foreign_keys=ON; PRAGMA foreign_key_check;"
sqlite3 app.db "PRAGMA cache_size=-65536; PRAGMA optimize;"

Внешние ключи включаются на каждое соединение — это частая причина вопроса, почему каскадное удаление не работает. busy_timeout задаёт ожидание блокировки вместо мгновенной ошибки database is locked. synchronous=NORMAL в режиме WAL безопасен для целостности, но при отключении питания может потерять последние транзакции.

Конкурентность и транзакции

В SQLite один писатель и много читателей. Если транзакция начинается как отложенная, а потом пытается писать, она может получить SQLITE_BUSY при апгрейде блокировки. Начинайте изменяющие транзакции явно через BEGIN IMMEDIATE.

sqlite3 app.db "BEGIN IMMEDIATE; UPDATE accounts SET balance=balance-100 WHERE id=1; COMMIT;"
sqlite3 app.db "SELECT COUNT(*) FROM accounts WHERE balance = 0;"

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

Резервное копирование

Копировать файл базы через cp во время записи нельзя: получите битую копию. Используйте встроенный механизм .backup, который работает через online backup API и не блокирует чтение.

sqlite3 app.db ".backup '/var/backups/app-$(date +%F).db'"
sqlite3 app.db "VACUUM INTO '/var/backups/app-compact.db';"
sqlite3 /var/backups/app-$(date +%F).db "PRAGMA quick_check;"

VACUUM INTO создаёт компактную копию без изменения исходника, но требует свободного места. После копирования обязательно проверьте её целостность и протестируйте восстановление — непроверенный бэкап не считается бэкапом.

Диагностика и запросы

Планировщик SQLite хорошо объясняет себя. Команда EXPLAIN QUERY PLAN показывает, используется ли индекс, происходит ли полный скан и почему сортировка выполняется отдельно.

sqlite3 app.db "PRAGMA integrity_check;"
sqlite3 app.db ".schema events"
sqlite3 app.db "EXPLAIN QUERY PLAN SELECT id, title FROM notes WHERE owner_id=42 ORDER BY created_at DESC LIMIT 20;"
sqlite3 app.db "CREATE INDEX IF NOT EXISTS idx_notes_owner_created ON notes(owner_id, created_at DESC);"

Составной индекс по фильтру и сортировке часто убирает и скан, и временную сортировку. Не индексируйте всё подряд: каждый индекс замедляет запись и занимает место. Периодически запускайте ANALYZE, чтобы статистика соответствовала данным.

Полнотекстовый поиск и строгие таблицы

Для поиска по тексту используйте FTS5: виртуальная таблица хранит собственный индекс и обновляется триггерами. Это дешевле, чем LIKE с процентами, который всегда читает всю таблицу. Строгие таблицы STRICT запрещают хранить число в текстовом столбце и ловят ошибки на этапе записи.

sqlite3 app.db "CREATE VIRTUAL TABLE notes_fts USING fts5(title, body, content='notes', content_rowid='id');"
sqlite3 app.db "INSERT INTO notes_fts(rowid, title, body) SELECT id, title, body FROM notes;"
sqlite3 app.db "CREATE TABLE tags (id INTEGER PRIMARY KEY, name TEXT NOT NULL) STRICT;"

FTS5 не заменяет обычные индексы: он ищет по словам, но не ускоряет точные сравнения и соединения. Для русскоязычных текстов задайте токенизатор unicode61 и проверьте, как обрабатываются дефисы и окончания.

Типичные ошибки

Хранить базу на NFS или SMB — прямой путь к повреждению блокировок. Держать один файл на сетевом диске и открывать его из нескольких контейнеров тоже нельзя без внешней координации. Крупные бинарные объекты лучше выносить в файловую систему, а в таблице хранить путь и хеш.

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

Итог

SQLite — полноценная СУБД для одного узла и умеренного числа писателей. Файл удобнее сервера в настольных, встраиваемых и читающих сценариях, но проигрывает при конкурентной записи, удалённом доступе и требованиях отказоустойчивости. Включайте WAL, настраивайте busy_timeout, не копируйте файл на ходу и проверяйте бэкапы — тогда одного файла хватит надолго. Это особенно заметно на медленных дисках и в контейнерах.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте