SQLite или PostgreSQL: какую базу выбрать

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

Стек и технологии Обновлено

Коротко

SQLite — база в одном файле рядом с приложением: нечего устанавливать и администрировать, очень быстрое чтение, и её хватает большинству сайтов, блогов, каталогов и внутренних инструментов на одном сервере. PostgreSQL — отдельный сервер баз данных: много одновременных записей, доступ по сети с нескольких серверов, пользователи и права, строгие типы и расширения вроде PostGIS и pgvector. Берите SQLite, когда проект живёт на одном сервере и записей умеренно; PostgreSQL — когда пишущих много, серверов несколько или данным нужны свои правила. Начать на SQLite и переехать позже — нормально, если SQL с первого дня переносимый.

Коротко: что выбрать

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

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

  • Один сервер, в основном чтение — SQLite
  • Много пишущих или серверов — PostgreSQL
  • Не уверены — SQLite с переносимым SQL

SQLite и PostgreSQL: подробное сравнение

Четырнадцать критериев рядом — от того, как хранятся данные, до копий и хостинга.

КритерийSQLitePostgreSQL
Где живут данные один файл рядом с кодом отдельный сервер баз данных
Установка нет — библиотека в языке сервер, пользователи, настройки
Чтение очень быстрое, без сети быстрое, через подключение
Одновременная запись один пишущий за раз много, блокировки на строках
Несколько серверов приложения нет, одна машина да, по сети
Пользователи и права только права на файл роли, права вплоть до строк
Типы мягкие по умолчанию, строгие с STRICT строгие всегда
Изменение схемы ограниченный ALTER TABLE полное, внутри транзакции
JSON функции и оператор ->> jsonb с индексами
Полнотекстовый поиск FTS5, без морфологии встроенный, со словарями языков
Расширения немного PostGIS, pgvector, TimescaleDB и сотни других
Репликация внешние инструменты: Litestream, LiteFS встроенная
Резервные копии копия файла через .backup pg_dump, восстановление на любой момент
Хостинг везде, где работает код VPS или управляемый сервис

Какая база под какой проект

Двенадцать типичных проектов с рекомендацией и причиной.

ПроектБратьПочему
Корпоративный сайт или блог SQLite почти одно чтение
Каталог или справочник SQLite быстрое чтение, копия — один файл
Внутренний инструмент для команды SQLite мало пишущих, нечего администрировать
Прототип или MVP SQLite старт сегодня, переезд, когда вырастет
Интернет-магазин с заказами PostgreSQL заказы и остатки меняются в один момент
SaaS с множеством клиентов PostgreSQL много пишущих, права вплоть до строк
CRM или учёт PostgreSQL строгие типы и постоянная запись
Приложение на нескольких серверах PostgreSQL один файл не делят по сети
Карты и геоданные PostgreSQL PostGIS
Поиск по смыслу, RAG PostgreSQL pgvector рядом с данными
Расширение браузера или программа SQLite база внутри приложения, без сервера
Аналитика по сети PostgreSQL аналитики подключаются своими инструментами

Признаки, что SQLite уже мало

SQLite редко отказывает внезапно — она подаёт сигналы. Любой из них — повод планировать переезд.

  1. 01

    «database is locked» в журналах

    Ошибка появляется даже с WAL и busy_timeout — пишущие слишком долго стоят в очереди.

  2. 02

    Второй сервер

    Приложению нужно работать на двух машинах, и обе должны писать.

  3. 03

    Доступ по сети

    Аналитикам, отчётам или другому сервису нужно читать данные напрямую.

  4. 04

    Разные права

    Кто-то должен видеть только свои строки, и одного кода как защиты мало.

  5. 05

    Нужно расширение

    Карты, векторный поиск или временные ряды — PostGIS, pgvector, TimescaleDB.

  6. 06

    Поиск с морфологией

    Пользователи ищут по формам слова, а FTS5 находит только точные.

Разница в SQL: 3 примера

Что работает одинаково в обеих, где они расходятся и как переехать. Каждый пример выполнен в SQLite 3.40 и PostgreSQL 16.

Переносимый SQL

Таблица и upsert с RETURNING, которые выполняются без правок в обеих базах, — основа лёгкого переезда.

upsert.sql
-- Один файл, две базы: выполняется без правок в SQLite 3.35+ и PostgreSQL
CREATE TABLE products (
    sku   TEXT PRIMARY KEY,
    title TEXT NOT NULL,
    price INTEGER NOT NULL CHECK (price >= 0),
    stock INTEGER NOT NULL DEFAULT 0
);

-- Добавить товар, а если артикул уже есть — обновить цену и прибавить остаток
INSERT INTO products (sku, title, price, stock)
VALUES ('A-100', 'Дубовый стол', 24000, 3)
ON CONFLICT (sku) DO UPDATE
    SET price = excluded.price,
        stock = products.stock + excluded.stock
RETURNING sku, price, stock;
-- первый запуск: A-100 | 24000 | 3
-- второй запуск: A-100 | 24000 | 6

Типы: главное различие

Один INSERT — три результата. Обычная таблица SQLite хранит текст в числовой колонке, STRICT и PostgreSQL отказывают.

types.sql
-- Тот же INSERT, но вместо числа слово
INSERT INTO products (sku, title, price)
VALUES ('C-300', 'Берёзовый стул', 'бесплатно');

-- SQLite, обычная таблица: строка сохранена, price = 'бесплатно'
--   (CHECK тоже проходит: в SQLite любой текст «больше» любого числа)
-- SQLite, таблица объявлена STRICT:
--   cannot store TEXT value in INTEGER column products.price
-- PostgreSQL:
--   invalid input syntax for type integer: "бесплатно"

Перенос таблицы

Выгрузка в CSV, загрузка через \copy и сдвиг счётчика id — шаг, который чаще всего забывают.

move.sh
# Перенос таблицы из SQLite в PostgreSQL. Таблица в PostgreSQL создаётся заранее:
#   CREATE TABLE products (id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
#                          sku text UNIQUE NOT NULL, title text NOT NULL, price integer NOT NULL);

# 1. Выгрузить из SQLite в CSV
sqlite3 -header -csv app.db "SELECT id, sku, title, price FROM products" > products.csv

# 2. Загрузить в PostgreSQL, сохранив те же id
psql "$DATABASE_URL" -c "\copy products (id, sku, title, price) FROM 'products.csv' CSV HEADER"

# 3. Сдвинуть счётчик id за загруженные строки,
#    иначе следующий INSERT упадёт с «duplicate key value violates unique constraint»
psql "$DATABASE_URL" -c "SELECT setval(pg_get_serial_sequence('products', 'id'), max(id)) FROM products"

Переезд с SQLite на PostgreSQL: что учесть

Данные переезжают легко; сюрпризы — в том, что SQLite прощала, а PostgreSQL нет.

  1. 01

    Сначала проверить типы

    Найти текст в числовых колонках через typeof() и вычистить до переезда, иначе загрузка встанет на первой плохой строке.

  2. 02

    pgloader для больших баз

    Он читает файл SQLite напрямую и переносит схему и данные одной командой.

  3. 03

    Счётчики id

    После загрузки с явными id каждый счётчик сдвигают через setval.

  4. 04

    Логические значения и даты

    SQLite хранит их как 0/1 и текст — в PostgreSQL они становятся boolean и timestamptz.

  5. 05

    LIKE и регистр

    В SQLite LIKE не различает регистр латиницы, в PostgreSQL различает — поиску нужен ILIKE.

  6. 06

    Двойные кавычки

    SQLite часто принимает строку в двойных кавычках; PostgreSQL читает её как имя колонки и падает.

Частые ошибки при выборе между ними

  1. PostgreSQL «на всякий случай»

    Отдельный сервер, обновления и копии ради сайта, который только читает.

  2. SQLite на сетевом диске

    Блокировки по сети ненадёжны — файл портится.

  3. Винить SQLite без WAL

    Большинство жалоб «SQLite тормозит» исчезают после WAL и busy_timeout.

  4. Годы на мягких типах

    Без STRICT копятся грязные данные и всплывают в день переезда.

  5. SQL, привязанный к одной базе

    Особые функции по всему коду превращают простой переезд в переписывание.

  6. Переезд ради названия

    Если медленно из-за запросов и индексов, PostgreSQL тоже будет медленной.

Вопросы о SQLite и PostgreSQL

Подходит ли SQLite для рабочего проекта?

Да, для проекта на одном сервере с умеренной записью — с WAL, busy_timeout и регулярными копиями.

Какая быстрее?

SQLite — на чтении в одном приложении, потому что нет сети; PostgreSQL — когда много пишут одновременно.

Какой трафик выдержит SQLite?

Чтение редко становится пределом; предел — сколько записей приходит в один момент.

Можно начать на SQLite и переехать позже?

Да, если SQL переносимый, а таблицы STRICT, — тогда переезд занимает часы, а не недели.

Поддерживает ли SQLite JSON?

Да, функции и оператор ->>; PostgreSQL добавляет jsonb с индексами по полям.

Как делать копии каждой?

SQLite — .backup или Litestream, а не простое копирование живого файла; PostgreSQL — pg_dump и архив WAL.

А как же MySQL?

Это серверная база, как PostgreSQL; чем они отличаются — в отдельном сравнении.

Форма

Подобрать
базу

Работаю с обеими: SQLite — для проектов на одном сервере, PostgreSQL — когда нагрузке, команде или данным нужно больше. Расскажите о проекте — отвечу в течение рабочего дня.

Или пишите на [email protected]