SQLite — самая распространённая база данных в мире. Она есть в Android и iOS, в браузерах Chrome и Firefox, в Windows и macOS, в стандартной библиотеке Python. По оценке разработчиков SQLite, в активном использовании находится больше триллиона баз SQLite.
При этом SQLite не похожа на привычные СУБД: у неё нет сервера, сетевых подключений, пользователей и паролей. Вся база — один файл на диске.
Архитектура SQLite: почему один файл
PostgreSQL, MySQL и MongoDB работают по клиент-серверной схеме: отдельный процесс слушает соединения, получает запросы по сети и возвращает результаты. Сервер нужно настраивать, он потребляет память даже в простое, а каждый запрос проходит через сеть или сокет.
SQLite — это библиотека, которая встраивается прямо в приложение. Вызов sqlite3.connect('database.db') открывает файл и работает с ним через системные вызовы ОС, без сетевого взаимодействия.
Традиционная СУБД: SQLite:
┌──────────┐ ┌────────────────────────┐
│ App │──TCP──►DB │ App + SQLite lib │
│ │◄──TCP──Server │ │
└──────────┘ │ ┌──────────────────┐ │
│ │ database.db │ │
│ └──────────────────┘ │
└────────────────────────┘
Что даёт встроенная архитектура:
- нет сетевой задержки — запрос выполняется в процессе приложения;
- нет отдельного сервиса, который надо поддерживать;
- базу можно перенести или заархивировать как обычный файл (но копировать «живую» базу нужно правильно — см. раздел про бэкап);
- не нужны права администратора;
- нулевая конфигурация.
SQLite поддерживает полноценный SQL
Распространённое заблуждение — «SQLite не настоящая база». На деле она реализует большую часть SQL и многие расширения:
-- ACID-транзакции
BEGIN TRANSACTION;
INSERT INTO orders (user_id, total) VALUES (42, 1500.00);
INSERT INTO order_items (order_id, product_id, qty) VALUES (last_insert_rowid(), 7, 2);
COMMIT;
-- Оконные функции (с SQLite 3.25.0)
SELECT
name,
salary,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rank_in_dept
FROM employees;
-- Рекурсивные CTE
WITH RECURSIVE categories AS (
SELECT id, name, parent_id FROM category WHERE id = 1
UNION ALL
SELECT c.id, c.name, c.parent_id
FROM category c
JOIN categories p ON c.parent_id = p.id
)
SELECT * FROM categories;
-- JSON-функции (встроены по умолчанию с SQLite 3.38.0)
SELECT json_extract(metadata, '$.tags[0]') AS first_tag
FROM articles
WHERE json_extract(metadata, '$.published') = 1;
Чего в SQLite нет или что ограничено: хранимых процедур, полноценного ALTER TABLE (добавление, переименование и с версии 3.35 удаление колонки есть, а смену типа и ограничений — нет), строгой типизации по умолчанию (есть STRICT-таблицы с 3.37). RIGHT JOIN и FULL OUTER JOIN поддерживаются начиная с 3.39 — на старых версиях их нет.
WAL-режим: как SQLite справляется с конкурентностью
В SQLite в каждый момент пишет только одно соединение. В режиме журнала по умолчанию запись ещё и блокирует читателей. Режим WAL (Write-Ahead Logging) это смягчает:
-- Включить WAL (настройка сохраняется в файле базы)
PRAGMA journal_mode=WAL;
-- Типичные настройки для веб-приложения
PRAGMA synchronous=NORMAL; -- меньше fsync; в WAL-режиме база не портится при сбое
PRAGMA cache_size=-64000; -- около 64 МБ кэша страниц
PRAGMA foreign_keys=ON; -- внешние ключи по умолчанию выключены
PRAGMA busy_timeout=5000; -- ждать до 5 секунд, если база занята
В WAL-режиме:
- читатели работают параллельно с писателем и не блокируют его;
- писатель по-прежнему один в каждый момент времени;
- запись обычно быстрее, чем в режиме rollback journal.
Пропускная способность зависит от диска, размера транзакций и настроек, поэтому меряйте на своей нагрузке. Для большинства веб-приложений с умеренной записью одного писателя хватает с запасом.
Важное следствие для бэкапа: в WAL-режиме свежие транзакции какое-то время живут в файле database.db-wal. Копия одного database.db через cp может оказаться неполной или несогласованной.
Ренессанс SQLite
Долгое время SQLite считали «не для продакшна». Сейчас вокруг неё появилось несколько инструментов, закрывающих исторические ограничения:
Turso / libSQL — открытый форк SQLite с репликами, встроенными (embedded) репликами, HTTP-доступом и векторным поиском; Turso Cloud — managed-сервис поверх него. Подробнее — в статье что такое Turso.
Cloudflare D1 — managed SQLite для Cloudflare Workers: доступ через binding, без TCP-подключения.
LiteFS — открытый проект Fly.io для репликации SQLite через FUSE: один писатель и read-реплики в других регионах.
PocketBase — готовый бэкенд на SQLite: база, авторизация, файлы и API в одном бинарнике. См. что такое PocketBase.
Litestream — непрерывная репликация WAL в S3: позволяет восстановить базу почти на любой момент времени.
# Litestream: непрерывная репликация SQLite в S3
litestream replicate /data/database.db s3://my-bucket/db
Когда SQLite — правильный выбор
Мобильные приложения. SQLite встроена в Android (с ней работает Room) и iOS (стандартное хранилище Core Data). Для локального хранения на устройстве это стандарт.
Десктопные приложения. Electron, Tauri, Qt: настройки, кэш и пользовательские данные без установки СУБД.
Один сервер и предсказуемая нагрузка. Если проект живёт на одном VPS и в основном читает данные, SQLite избавляет от отдельного сервера БД и сетевых задержек.
PocketBase и аналоги. SQLite встроена, отдельную БД обслуживать не нужно.
Edge. Cloudflare D1 и Turso дают SQLite рядом с пользователем.
Тесты. База в памяти (:memory:) создаётся практически мгновенно — удобно для тестов против настоящей СУБД.
Распространение данных. SQLite-файл — удобный формат для передачи наборов данных вместо CSV: сразу с SQL-запросами.
Когда SQLite — неправильный выбор
- Несколько серверов должны писать в одну базу.
- Много конкурентной записи: все записи выстраиваются в очередь к одному писателю.
- Очень большие базы с тяжёлой аналитикой — технический предел около 281 ТБ, но обслуживать такой файл (бэкап,
VACUUM, перенос) неудобно задолго до этого предела. - Нужны специфические возможности PostgreSQL: PostGIS, pgvector, row-level security, богатые типы.
- База должна быть доступна по сети нескольким приложениям или людям напрямую.
Сравнение SQLite с PostgreSQL и MySQL
| Параметр | SQLite | PostgreSQL | MySQL | |---|---|---|---| | Тип | Встроенная | Клиент-серверная | Клиент-серверная | | Настройка | Нулевая | Умеренная | Умеренная | | Конкурентная запись | Один писатель (WAL) | Высокая (MVCC) | Высокая (InnoDB) | | Транзакции | ACID | ACID | ACID (InnoDB) | | Масштабирование | Вертикальное | Вертикальное + реплики | Вертикальное + реплики | | JSON | JSON-функции, JSONB с 3.45 | JSONB с индексами | Тип JSON и функции | | Полнотекстовый поиск | FTS5 | Встроенный, со словарями | Встроенный | | Геоданные | Расширение SpatiaLite | PostGIS | Встроенные spatial-типы | | Хранение | Один файл | Директория данных | Директория данных | | Типизация | Type affinity, STRICT-таблицы | Строгая | Строгая | | Типичное применение | Embedded, edge, MVP, один сервер | Веб, SaaS, аналитика | Веб, CMS |
Подробное сравнение с NoSQL-вариантами — в статье SQLite, PostgreSQL, MySQL или NoSQL.
SQLite в продакшне: практические настройки
# Python
import sqlite3
def get_db_connection(db_path: str) -> sqlite3.Connection:
conn = sqlite3.connect(db_path, check_same_thread=False)
conn.row_factory = sqlite3.Row # доступ к колонкам по имени
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA synchronous=NORMAL")
conn.execute("PRAGMA foreign_keys=ON")
conn.execute("PRAGMA cache_size=-32000") # около 32 МБ
conn.execute("PRAGMA busy_timeout=10000") # 10 секунд
conn.execute("PRAGMA temp_store=MEMORY")
return conn
// Node.js: better-sqlite3 (в Bun — import { Database } from 'bun:sqlite')
import Database from 'better-sqlite3'
const db = new Database('production.db')
db.pragma('journal_mode = WAL')
db.pragma('synchronous = NORMAL')
db.pragma('foreign_keys = ON')
db.pragma('busy_timeout = 10000')
db.pragma('cache_size = -32000')
// Подготовленные выражения защищают от SQL-инъекций
const getUser = db.prepare('SELECT * FROM users WHERE id = ?')
const user = getUser.get(userId)
Бэкап SQLite: копия должна быть согласованной
Копирование файла (cp database.db backup.db) во время записи может дать повреждённую или неполную копию. Безопасные варианты:
# 1. Online backup API через CLI — безопасно при активной записи
sqlite3 database.db ".backup 'backup.db'"
# 2. VACUUM INTO (SQLite 3.27+): компактная копия, целевой файл не должен существовать
sqlite3 database.db "VACUUM INTO 'backup.db'"
# 3. Проверка копии
sqlite3 backup.db "PRAGMA integrity_check;"
Подробно, с cron и выгрузкой в S3, — в статье как сделать бэкап SQLite без повреждения. Чем отличаются непрерывная репликация и периодические копии — в сравнении Litestream vs dbsend.
Если нужны история версий и оповещения о пропущенных бэкапах, подойдёт бэкап SQLite в dbsend. CLI сам делает согласованную копию через VACUUM INTO, поэтому загружать можно рабочий файл:
dbsend backup ./database.db -d "$DBSEND_DATABASE_ID" -e sqlite -l "sqlite-$(date +%F-%H%M)"
При загрузке файл проверяется PRAGMA quick_check, а при dbsend restore — PRAGMA integrity_check и сверкой SHA-256.
Частые вопросы
Можно ли использовать SQLite в продакшне?
Да. Rails 8 предлагает SQLite как полноценный вариант для продакшна, PocketBase целиком построен на SQLite, Cloudflare D1 — managed SQLite. Условия: один основной сервер, включённый WAL-режим, busy_timeout и автоматические бэкапы с регулярной проверкой восстановления.
Насколько большой может быть база SQLite?
Технический предел — около 281 ТБ. На практике ограничивает не формат, а обслуживание: бэкап, VACUUM и перенос большого файла занимают время, а тяжёлые аналитические запросы в SQLite медленнее, чем в специализированных системах. Если база растёт быстро и запись конкурентная, планируйте переход на PostgreSQL заранее.
SQLite потокобезопасна?
В стандартных сборках (SQLITE_THREADSAFE=1, режим serialized) SQLite можно использовать из нескольких потоков. На одном соединении запросы всё равно выполняются по очереди, поэтому обычно каждому потоку дают своё соединение. WAL-режим позволяет читателям работать параллельно с писателем.
Чем SQLite отличается от Turso?
Turso строится на libSQL — открытом форке SQLite с репликами, встроенными репликами, HTTP-доступом и векторным поиском. Turso Cloud — managed-сервис поверх него. Базы libSQL используют формат файлов SQLite, но новые возможности форка обычным sqlite3 недоступны.
Есть ли у SQLite полнотекстовый поиск?
Да, расширение FTS5:
CREATE VIRTUAL TABLE articles_fts USING fts5(title, content);
SELECT * FROM articles_fts WHERE articles_fts MATCH 'база данных';
Стемминга для русского языка из коробки нет, поэтому поиск по словоформам слабее, чем в PostgreSQL с конфигурацией russian. Для серьёзного поиска по русскому тексту смотрите на Meilisearch или PostgreSQL.
Как мигрировать схему в SQLite?
Простые операции делает ALTER TABLE: добавить, переименовать и (с 3.35) удалить колонку. Для смены типа или ограничений создайте новую таблицу с нужной схемой, перенесите данные и переименуйте:
BEGIN;
CREATE TABLE users_new (id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE);
INSERT INTO users_new (id, name, email) SELECT id, name, email FROM users;
DROP TABLE users;
ALTER TABLE users_new RENAME TO users;
COMMIT;
CREATE TABLE ... AS SELECT для этого не подходит: он теряет первичные ключи и ограничения. Индексы и триггеры пересоздайте после переноса. Автоматизировать миграции помогают drizzle-kit, Prisma Migrate или Alembic в batch-режиме. Перед миграцией сделайте бэкап.



