Короткий ответ: PostgreSQL для продакшена, SQLite для локальной разработки и тестирования. MySQL или MariaDB — если под них уже есть инфраструктура и опыт. Ниже — почему так и как настроить каждый вариант.
Какие базы данных поддерживает Django
Django «из коробки» поддерживает пять СУБД:
- PostgreSQL — рекомендуется как основной выбор
- MySQL и MariaDB — два отдельных встроенных бэкенда, широко используются
- SQLite — default при
startproject, для разработки и тестирования - Oracle — для корпоративных legacy-систем
Через сторонние пакеты:
- Microsoft SQL Server — через
mssql-django - MongoDB — через
djongoилиmongoengine(неофициально, лучше не надо) - CockroachDB — совместим с PostgreSQL API
SQLite: с чего начинает каждый Django-проект
Когда вы создаёте Django-проект через django-admin startproject, в settings.py уже настроен SQLite:
# settings.py — Django default
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
Никаких зависимостей, никакой установки — просто файл db.sqlite3 в корне проекта.
Когда SQLite — правильный выбор
- Локальная разработка: быстрый старт, не нужен Docker или отдельный сервис
- Юнит-тесты: SQLite в памяти быстро создаётся и не требует сервера (но тесты на другой СУБД, чем в продакшене, могут пропустить ошибки)
- Маленькие проекты: личный блог, портфолио, внутренний инструмент с 1-2 пользователями
- CI/CD pipeline: тесты без внешних зависимостей
# Быстрый in-memory SQLite для тестов
# settings_test.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
}
Ограничения SQLite в Django
С SQLite недоступно:
- всё из
django.contrib.postgres: ArrayField, HStoreField, range-поля, PostgreSQL-поиск (JSONFieldпри этом работает на всех бэкендах начиная с Django 3.1); - параллельная запись: в каждый момент пишет только один процесс, WAL-режим лишь позволяет читать во время записи;
- часть ALTER TABLE — Django обходит это пересозданием таблицы при миграции, что на больших таблицах долго.
Для небольших проектов SQLite в продакшене — рабочий вариант. С Django 5.1 PRAGMA и режим транзакций можно задать прямо в настройках:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
'OPTIONS': {
'init_command': 'PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL; PRAGMA busy_timeout=5000;',
'transaction_mode': 'IMMEDIATE',
},
}
}
Не забудьте про бэкап: файл db.sqlite3 нельзя просто копировать во время работы приложения — см. как сделать бэкап SQLite без повреждения.
PostgreSQL: стандарт для Django в продакшене
PostgreSQL — самый полно поддерживаемый Django бэкенд: модуль django.contrib.postgres добавляет поля, индексы, ограничения и поиск, которых нет для других СУБД.
Установка и настройка
# Django 4.2+: psycopg 3 — рекомендуемый драйвер для новых проектов
pip install "psycopg[binary]"
# или классический psycopg2
pip install psycopg2-binary # бинарная сборка, удобно для разработки
pip install psycopg2 # сборка из исходников, нужны libpq-dev и компилятор
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'myapp_db',
'USER': 'myapp_user',
'PASSWORD': 'secure_password',
'HOST': 'localhost',
'PORT': '5432',
'CONN_MAX_AGE': 60, # держать соединение открытым до 60 с (это не пул)
'OPTIONS': {
'connect_timeout': 5,
}
}
}
Через переменную окружения (рекомендуется):
pip install dj-database-url
import dj_database_url
DATABASES = {
'default': dj_database_url.config(
default='postgresql://user:password@localhost:5432/myapp_db',
conn_max_age=600
)
}
PostgreSQL-специфичные возможности Django ORM
Именно ради этих фич выбирают PostgreSQL:
JSONField (нативный, с индексами)
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=200)
attributes = models.JSONField(default=dict) # Нативный JSONB в PostgreSQL
# Поиск по JSON-полю
Product.objects.filter(attributes__brand='Apple')
Product.objects.filter(attributes__ram__gte=16)
Product.objects.filter(attributes__tags__contains=['M3'])
ArrayField
from django.contrib.postgres.fields import ArrayField
class Article(models.Model):
title = models.CharField(max_length=200)
tags = ArrayField(models.CharField(max_length=50), default=list)
# Запросы
Article.objects.filter(tags__contains=['python'])
Article.objects.filter(tags__overlap=['django', 'postgresql'])
Article.objects.filter(tags__len__gte=3)
Full-text Search
from django.contrib.postgres.search import SearchVector, SearchQuery, SearchRank
# Полнотекстовый поиск с рангом
vector = SearchVector('title', weight='A') + SearchVector('content', weight='B')
query = SearchQuery('база данных')
results = Article.objects.annotate(
rank=SearchRank(vector, query)
).filter(rank__gte=0.1).order_by('-rank')
HStoreField (ключ-значение)
from django.contrib.postgres.fields import HStoreField
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
settings = HStoreField(default=dict)
profile.settings['theme'] = 'dark'
profile.save()
Window Functions
from django.db.models import Window, F
from django.db.models.functions import Rank
# Ранжирование пользователей по баллам
User.objects.annotate(
rank=Window(
expression=Rank(),
order_by=F('score').desc()
)
)
Constraint с исключениями (ExclusionConstraint)
from django.contrib.postgres.constraints import ExclusionConstraint
from django.contrib.postgres.fields import DateTimeRangeField, RangeOperators
# Требует расширения btree_gist (BtreeGistExtension в миграции) для сравнения room через GiST
# Запрет пересечения временных слотов (для бронирования)
class Booking(models.Model):
room = models.ForeignKey(Room, on_delete=models.CASCADE)
time_range = DateTimeRangeField()
class Meta:
constraints = [
ExclusionConstraint(
name='no_overlapping_bookings',
expressions=[
('room', RangeOperators.EQUAL),
('time_range', RangeOperators.OVERLAPS),
]
)
]
MySQL / MariaDB: для legacy и специфических требований
Настройка
pip install mysqlclient
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'myapp_db',
'USER': 'myapp_user',
'PASSWORD': 'password',
'HOST': 'localhost',
'PORT': '3306',
'OPTIONS': {
'charset': 'utf8mb4',
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
}
}
}
Важные нюансы MySQL с Django
Всегда используйте utf8mb4, не utf8 — иначе нельзя хранить эмодзи:
CREATE DATABASE myapp_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
Включайте строгий режим (STRICT_TRANS_TABLES): без него MySQL молча обрезает слишком длинные значения, и Django выдаёт предупреждение mysql.W002.
Что не работает в MySQL (по сравнению с PostgreSQL)
ArrayField,HStoreField— нет поддержкиdjango.contrib.postgres.search— нет; полнотекстовый поиск MySQL доступен только через raw SQL- Оконные функции есть с MySQL 8.0 и MariaDB 10.2, в старых версиях — нет
- JSONField поддерживается, но без JSONB-индексов, как в PostgreSQL
- Ограничение длины ключа индекса: 3072 байта в InnoDB с форматом строк DYNAMIC, 767 байт в старых конфигурациях — длинные
CharField(unique=True)в utf8mb4 могут не влезть
Сравнительная таблица
| Критерий | PostgreSQL | MySQL/MariaDB | SQLite |
|---|---|---|---|
| Продакшен | Да | Да | Только малые проекты |
| Django-фичи | Полный набор | Частичный | Минимальный |
| JSONField | JSONB с GIN-индексами | JSON | Да (через JSON1, хранится как текст) |
| Full-text Search в ORM | Да (contrib.postgres.search) | Нет, только raw SQL | Нет (FTS5 — только raw SQL) |
| Параллельные записи | MVCC (отлично) | Хорошо | Ограниченно |
| Транзакции | ACID | ACID | ACID |
| Масштабирование чтения | Реплики, расширения | Реплики, кластеры | Нет |
| Лицензия | PostgreSQL (свободная) | GPL | Public Domain |
| Российский хостинг | Везде | Везде | Везде |
| Сложность настройки | Средняя | Средняя | Нет |
Workflow: SQLite локально, PostgreSQL в продакшене
Стандартная практика — разные настройки для разных окружений:
# settings/base.py
import os
import dj_database_url
# settings/development.py
from .base import *
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# settings/production.py
from .base import *
DATABASES = {
'default': dj_database_url.config(
default=os.environ.get('DATABASE_URL'),
conn_max_age=600,
ssl_require=True
)
}
Но Django ORM гарантирует: тот же Python-код работает с обеими базами. Разница только в настройках.
Пул соединений: важно для продакшена
По умолчанию Django открывает новое соединение при каждом запросе. В продакшене используйте пул:
pgBouncer (внешний пул для PostgreSQL)
apt-get install pgbouncer
; /etc/pgbouncer/pgbouncer.ini
[databases]
myapp_db = host=127.0.0.1 port=5432 dbname=myapp_db
[pgbouncer]
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20
# Подключение через pgBouncer
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'HOST': 'localhost',
'PORT': '6432', # Порт pgBouncer, не PostgreSQL
'NAME': 'myapp_db',
# В режиме transaction серверные курсоры нужно отключить
'DISABLE_SERVER_SIDE_CURSORS': True,
# ...
}
}
Встроенный пул Django 5.1+ (psycopg 3)
pip install "psycopg[binary,pool]"
# settings.py — пул через psycopg 3 (Django 5.1+); CONN_MAX_AGE при этом должен быть 0
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
# NAME, USER, PASSWORD, HOST, PORT — как выше
'OPTIONS': {
'pool': {
'min_size': 2,
'max_size': 20,
'reconnect_timeout': 5,
}
}
}
}
Индексы и оптимизация Django ORM
class Article(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True) # Автоматический индекс
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
status = models.CharField(max_length=20, db_index=True)
class Meta:
indexes = [
# Составной индекс
models.Index(fields=['author', 'status'], name='author_status_idx'),
# Частичный индекс (только опубликованные)
models.Index(
fields=['created_at'],
name='published_articles_idx',
condition=models.Q(status='published')
),
]
Проблема N+1 запросов
# ПЛОХО: N+1 запрос для каждой статьи
articles = Article.objects.all()
for article in articles:
print(article.author.name) # Отдельный SELECT для каждой статьи!
# ХОРОШО: select_related для ForeignKey
articles = Article.objects.select_related('author').all()
# ХОРОШО: prefetch_related для ManyToMany
articles = Article.objects.prefetch_related('tags').all()
Бэкапы базы для Django-проектов
Перед каждым деплоем с миграциями делайте дамп — откатить migrate с удалением столбца без копии нельзя.
# Дамп в custom-формате перед деплоем
pg_dump -h localhost -U myapp_user -Fc -f backup_before_deploy.dump myapp_db
# Восстановление в существующую базу с удалением текущих объектов
pg_restore -h localhost -U myapp_user -d myapp_db --clean --if-exists backup_before_deploy.dump
pg_restore читает только custom, directory и tar-форматы. Plain-дамп (.sql) восстанавливается через psql -v ON_ERROR_STOP=1 -f backup.sql. Все варианты восстановления — в статье как восстановить базу из бэкапа, а регулярные дампы по cron с выгрузкой в S3 — в статье бэкап PostgreSQL через pg_dump в S3.
Если хотите хранить версии вне сервера и видеть, что поменялось между ними, plain-дамп можно загружать в dbsend. Сервис не подключается к вашей базе — дамп делаете вы, CLI его загружает:
pg_dump --format=plain --no-owner "$DATABASE_URL" > /tmp/myapp.sql
dbsend backup /tmp/myapp.sql -d "$DBSEND_DATABASE_ID" -e postgresql_dump -l "deploy-$GIT_SHA"
Для db.sqlite3 достаточно dbsend backup db.sqlite3 -d "$DBSEND_DATABASE_ID" -e sqlite — CLI сам сделает консистентную копию работающей базы. Подробнее — на странице бэкапы PostgreSQL.
Рекомендации по типу проекта
Для прототипа и MVP:
- SQLite локально + PostgreSQL на VPS или в managed-сервисе (см. аналоги Railway в России)
Для SaaS-приложения:
- PostgreSQL с pgBouncer, JSONB для гибких данных, pg_trgm для поиска
Для e-commerce:
- PostgreSQL, обязательно транзакции, возможно TimescaleDB для аналитики
Для контентного сайта (много чтения, мало записи):
- PostgreSQL + Redis для кэша, полнотекстовый поиск через PostgreSQL FTS или Elasticsearch
Для инди-проекта одного разработчика:
- SQLite + Litestream или периодические версии в S3 — дёшево и достаточно для начала (сравнение — в статье Litestream vs dbsend)
Частые вопросы
Почему Django по умолчанию использует SQLite, если PostgreSQL лучше?
SQLite не требует настройки: python manage.py migrate работает сразу после startproject. PostgreSQL нужно установить, создать базу и пользователя. Django выбирает простоту старта.
Можно ли мигрировать с SQLite на PostgreSQL?
Да. Сделайте python manage.py dumpdata --natural-foreign --natural-primary -e contenttypes -e auth.Permission --indent 2 > data.json, переключите настройки на PostgreSQL, выполните migrate и затем loaddata data.json. На больших базах dumpdata медленный — тогда используют специализированные инструменты переноса. Перед переносом сохраните копию db.sqlite3.
MySQL или PostgreSQL для Django?
PostgreSQL, если нет конкретной причины выбрать MySQL (существующая инфраструктура, требования IT, опыт команды). С PostgreSQL доступен весь django.contrib.postgres.
Как ускорить тесты в Django?
Используйте --keepdb, чтобы не пересоздавать тестовую базу, и --parallel для параллельного запуска. SQLite в памяти даёт быстрые тесты, но если в продакшене PostgreSQL, запускайте тесты и на нём — хотя бы в CI.
Django работает с несколькими базами данных?
Да. В DATABASES можно описать несколько подключений, а маршрутизацию запросов задать через database routers. Это используют для реплик на чтение и разделения данных.
Как настроить read replica в Django?
Через DATABASE_ROUTERS: router-класс направляет чтение на реплику, а запись — на основной сервер. Учитывайте задержку репликации: сразу после записи читайте с основного сервера. Подробнее — в документации Django.
Нужен ли Redis вместе с PostgreSQL для Django?
Redis — это кэш, брокер для Celery и хранилище сессий; PostgreSQL — основное хранилище данных. Они не конкурируют. Типичный продакшен-стек: Django + PostgreSQL + Redis + Celery.



