Базы данных в Python — это не часть языка, а отдельные системы хранения (СУБД), с которыми программа на Python работает через библиотеку-драйвер: отправляет SQL-запросы и получает строки результата. СУБД бывает встраиваемой (SQLite работает внутри процесса Python и хранит базу в файле) или серверной (PostgreSQL, MySQL — отдельная программа, к которой подключаются по сети). Сама база хранит данные и следит за их целостностью, Python ими управляет. Ниже разберем, какие СУБД чаще всего используют с Python, какой драйвер ставить под каждую, чем драйвер отличается от ORM и как выбрать связку под задачу.
Содержание
- Три слоя: СУБД, драйвер, ORM
- Какие СУБД используют с Python и чем подключаться
- Минимальный рабочий пример: SQLite через sqlite3
- Тот же код для PostgreSQL через psycopg
- Параметры вместо f-строк: защита от SQL-инъекции
- SQLAlchemy: один код для разных СУБД
- Как выбрать связку
- Если не получилось
- Выводы
- Где применяется / связь с практикой
- FAQ
Примеры проверены 24.09.2026 на Python 3.14.7 с библиотеками psycopg 3.3.6, SQLAlchemy 2.0.54, PyMySQL 1.2.3 и PostgreSQL 16 в Docker.
Три слоя: СУБД, драйвер, ORM
Эти слова часто смешивают, поэтому сначала короткий словарь.
| Слой | Что это | Пример |
|---|---|---|
| СУБД | Программа, которая хранит данные и выполняет запросы | PostgreSQL, MySQL, SQLite |
| Драйвер | Библиотека Python, которая умеет «говорить» с конкретной СУБД | sqlite3, psycopg, PyMySQL |
| ORM / SQL-toolkit | Надстройка над драйвером: пишет SQL за вас или дает единый интерфейс к разным СУБД | SQLAlchemy, Django ORM |
Цепочка на одном примере: код вызывает cursor.execute(...) -> драйвер psycopg передает запрос по сети -> PostgreSQL выполняет его и возвращает строки -> драйвер превращает их в кортежи Python.
Драйверы реляционных СУБД следуют общему стандарту DB-API 2.0 (PEP 249): у всех есть connect(), курсор, execute(), fetchall(), commit() и rollback(). Поэтому код для SQLite и PostgreSQL выглядит почти одинаково. Отличаются детали, и о них ниже.
Какие СУБД используют с Python и чем подключаться
| СУБД | Тип | Драйвер | Установка | Когда брать |
|---|---|---|---|---|
| SQLite | Встраиваемая, база = один файл | sqlite3 |
встроен в стандартную библиотеку | учеба, утилиты, прототипы, настольные приложения |
| PostgreSQL | Клиент-серверная | psycopg (версия 3) |
pip install "psycopg[binary]" |
веб-сервисы, аналитика, много пользователей |
| MySQL / MariaDB | Клиент-серверная | PyMySQL (чистый Python) или mysqlclient |
pip install pymysql |
проекты, где уже стоит MySQL |
| MongoDB | Документная (NoSQL) | pymongo |
pip install pymongo |
данные в виде JSON-подобных документов |
| Redis | Хранилище «ключ-значение» в памяти | redis |
pip install redis |
кеш, очереди, счетчики |
MongoDB и Redis не используют SQL и не следуют DB-API: у их драйверов свой интерфейс. Дальше речь о реляционных базах, с которых обычно начинают.
Минимальный рабочий пример: SQLite через sqlite3
SQLite не требует сервера и установки, поэтому первый пример — на ней. Скрипт создает файл school.db, таблицу, добавляет три строки и читает часть из них.
import sqlite3
from contextlib import closing
students = [("Анна", 91), ("Борис", 78), ("Вера", 85)]
with closing(sqlite3.connect("school.db")) as conn:
with conn: # транзакция: commit при успехе, rollback при исключении
conn.execute(
"CREATE TABLE IF NOT EXISTS students ("
" id INTEGER PRIMARY KEY,"
" name TEXT NOT NULL,"
" score INTEGER NOT NULL CHECK (score BETWEEN 0 AND 100))"
)
conn.execute("DELETE FROM students") # чтобы повторный запуск давал тот же результат
conn.executemany("INSERT INTO students (name, score) VALUES (?, ?)", students)
rows = conn.execute(
"SELECT name, score FROM students WHERE score >= ? ORDER BY score DESC",
(80,),
).fetchall()
print(rows)
В консоли появится список студентов с баллом от 80:
[('Анна', 91), ('Вера', 85)]
Что здесь важно:
sqlite3.connect("school.db")открывает файл базы, а если его нет — создает. Путь относительный, от текущего каталога запуска.with conn:вsqlite3управляет только транзакцией и не закрывает соединение. Закрывает егоclosing(...). Это частая ловушка: соединение после блокаwith conn:остается открытым.?— место для параметра. Значения передаются отдельно, кортежем или списком кортежей.CHECKне даст записать балл 150: ограничения лучше держать в самой базе, а не только в коде.
Подробный разбор CRUD-запросов в SQLite — тема отдельного материала, здесь нам важен общий шаблон работы.
Тот же код для PostgreSQL через psycopg
Переход на серверную СУБД меняет три вещи: драйвер, строку подключения и знак параметра. Логика остается прежней.
import os
import psycopg
# например: postgresql://app:secret@localhost:5432/school
dsn = os.environ["DATABASE_URL"]
students = [("Анна", 91), ("Борис", 78), ("Вера", 85)]
with psycopg.connect(dsn) as conn: # при выходе: commit (или rollback при ошибке) и закрытие
with conn.cursor() as cur:
cur.execute(
"CREATE TABLE IF NOT EXISTS students ("
" id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,"
" name text NOT NULL,"
" score integer NOT NULL CHECK (score BETWEEN 0 AND 100))"
)
cur.execute("DELETE FROM students")
cur.executemany("INSERT INTO students (name, score) VALUES (%s, %s)", students)
cur.execute(
"SELECT name, score FROM students WHERE score >= %s ORDER BY score DESC",
(80,),
)
print(cur.fetchall())
Результат тот же: [('Анна', 91), ('Вера', 85)]. Сравним поведение двух драйверов.
| Что | sqlite3 |
psycopg 3 |
|---|---|---|
| Знак параметра | ? (или :name) |
%s (или %(name)s) |
with conn: |
commit/rollback, соединение остается открытым | commit/rollback и закрытие соединения |
| Где база | локальный файл | сервер по адресу из строки подключения |
| Автоинкремент | INTEGER PRIMARY KEY |
GENERATED ALWAYS AS IDENTITY |
Стиль параметров драйвер сообщает сам через атрибут paramstyle: у sqlite3 это qmark, у psycopg и PyMySQL — pyformat. Значит, для MySQL через PyMySQL запрос пишется с %s, как в примере для PostgreSQL. Сам пример с MySQL не прогонялся, поведение транзакций и with у PyMySQL сверяйте с его документацией.
Пароль в строке подключения не пишут в код: его берут из переменной окружения (DATABASE_URL) или менеджера секретов. Для приложения заводят отдельного пользователя СУБД с правами только на свою базу, а не подключаются суперпользователем.
Параметры вместо f-строк: защита от SQL-инъекции
Главное правило любого драйвера: значения от пользователя передаются параметрами, а не склеиваются в строку запроса. Вот что бывает, если склеить.
import sqlite3
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (login TEXT, is_admin INTEGER)")
conn.executemany("INSERT INTO users VALUES (?, ?)", [("anna", 0), ("root", 1)])
login = "nobody' OR '1'='1" # строка пришла от пользователя
bad = conn.execute(f"SELECT login FROM users WHERE login = '{login}'").fetchall()
print("f-строка:", bad)
good = conn.execute("SELECT login FROM users WHERE login = ?", (login,)).fetchall()
print("параметр:", good)
conn.close()
f-строка: [('anna',), ('root',)]
параметр: []
С f-строкой условие OR '1'='1' стало частью SQL, и запрос вернул всех пользователей, включая администратора. С параметром та же строка ищется как обычное значение логина, и такого логина нет.
Граница у этого правила есть: параметром передается только значение, а не имя таблицы или столбца. Если имя приходит извне, его сверяют с белым списком допустимых имен, а в psycopg собирают через psycopg.sql.Identifier. Параметризация защищает от инъекции, но не заменяет проверку входных данных и права пользователя в самой СУБД.
SQLAlchemy: один код для разных СУБД
Когда проект может сменить базу или растет, берут SQLAlchemy. Это не драйвер: она сама использует sqlite3, psycopg или PyMySQL, а сверху дает единый интерфейс. СУБД задается адресом в create_engine.
import os
from sqlalchemy import create_engine, text
url = os.environ.get("DATABASE_URL", "sqlite+pysqlite:///:memory:")
engine = create_engine(url)
with engine.begin() as conn: # транзакция: commit при успехе, rollback при ошибке
conn.execute(text("CREATE TABLE IF NOT EXISTS courses (title VARCHAR(100) NOT NULL, hours INTEGER NOT NULL)"))
conn.execute(text("DELETE FROM courses"))
conn.execute(
text("INSERT INTO courses (title, hours) VALUES (:title, :hours)"),
[{"title": "Python", "hours": 120}, {"title": "SQL", "hours": 40}],
)
rows = conn.execute(
text("SELECT title, hours FROM courses WHERE hours > :min ORDER BY hours"),
{"min": 30},
).all()
print(engine.dialect.name, [tuple(r) for r in rows])
Без переменной окружения скрипт работает с SQLite в памяти, а с DATABASE_URL=postgresql+psycopg://app:secret@localhost:5432/school — с PostgreSQL. Код не меняется, меняется только первое слово вывода:
sqlite [('SQL', 40), ('Python', 120)]
postgresql [('SQL', 40), ('Python', 120)]
Параметры здесь пишутся как :name для любой СУБД, SQLAlchemy сама переводит их в стиль драйвера. Для MySQL адрес выглядит как mysql+pymysql://user:password@host/db. Единый интерфейс не стирает различия диалектов: типы, функции и поведение ORDER BY с NULL у СУБД разные. Работа с моделями-классами (ORM) разобрана в статье «Введение в SQLAlchemy».
Как выбрать связку
| Задача | Что взять | Когда иначе |
|---|---|---|
| Учебный проект, скрипт, бот для себя | SQLite + sqlite3 |
много одновременных записей — переходить на сервер |
| Веб-сервис, API, несколько пользователей | PostgreSQL + psycopg |
компания уже живет на MySQL — PyMySQL или mysqlclient |
| Проект на Django | встроенный Django ORM | сложная аналитика — сырой SQL рядом с ORM |
| Нужно не привязываться к СУБД | SQLAlchemy поверх драйвера | маленький скрипт — хватит драйвера напрямую |
| Кеш, сессии, счетчики | Redis + redis |
данные нужно хранить долго и связно — реляционная база |
Начинать стоит с SQLite и чистого SQL: так видно, что на самом деле происходит в базе. ORM удобнее, когда основы SQL и транзакций уже понятны.
Если не получилось
ModuleNotFoundError: No module named 'psycopg2'. Установлен или ищется не тот пакет:psycopg2иpsycopg(версия 3) — разные библиотеки. Для новых проектов ставятpsycopgи импортируютpsycopg: для учебы и разработки прощеpip install "psycopg[binary]"(библиотеки внутри пакета), для production документация psycopg предпочитает локальную установкуpsycopg[c], связанную с системнымиlibpqиlibssl, чтобы они обновлялись вместе с системой.psycopg.OperationalError: ... Connection refused. Сервер не запущен или слушает другой порт. Проверьте хост и порт в строке подключения и что СУБД работает.sqlite3.OperationalError: no such table: students. Скрипт открыл другой файл: относительный путь считается от каталога запуска, а несуществующий файлsqlite3молча создает пустым. Используйте абсолютный путь. У базы:memory:таблицы живут только до закрытия соединения.- Данные записались, а после перезапуска их нет. Не было
commit. Используйтеwith conn:илиengine.begin(), которые фиксируют транзакцию сами.
Выводы
- База данных для Python — внешняя СУБД, а работа с ней идет через драйвер; ORM — необязательная надстройка над драйвером.
- Для SQLite драйвер
sqlite3встроен в Python, для PostgreSQL берутpsycopg, для MySQL —PyMySQLилиmysqlclient. - Реляционные драйверы следуют DB-API 2.0, но отличаются знаком параметра (
?или%s) и поведениемwith. - Значения всегда передаются параметрами, а не f-строкой: это защищает от SQL-инъекции.
- SQLAlchemy позволяет менять СУБД сменой адреса, но не отменяет различий диалектов.
Где применяется / связь с практикой
Освойте тему на практике
Работа с базой нужна почти в любом Python-проекте: Telegram-бот хранит пользователей, веб-сервис — заказы, парсер — собранные данные. Навык складывается из трех частей: SQL-запросы, работа с драйвером и транзакциями, выбор СУБД под задачу. Системно пройти основы языка вместе с хранением данных можно на курсе «Python-разработчик. Базовый уровень». Попробовать формат и разобрать отдельные темы с преподавателями помогут бесплатные открытые уроки Otus.
FAQ
Можно ли хранить данные просто в JSON-файле вместо базы?
Для небольших настроек — да. Но JSON-файл не дает одновременной безопасной записи, транзакций, индексов и ограничений, поэтому при росте данных или числа пользователей его заменяют на SQLite или серверную СУБД.
Чем psycopg отличается от psycopg2?
Это две разные библиотеки одного проекта Psycopg (psycopg2 создал Федерико Ди Грегорио, psycopg 3 написан его нынешним сопровождающим Даниэле Варраццо): psycopg — третья версия с новым API (например, with connect() закрывает соединение), psycopg2 — предыдущая, ее до сих пор много в существующих проектах. Для нового кода обычно берут psycopg.
Нужно ли знать SQL, если есть ORM?
Да. ORM генерирует SQL, и чтобы понимать, почему запрос медленный или вернул не то, нужно читать этот SQL и планы запросов.



