$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогSQLModel vs SQLAlchemy: что выбрать для Python-проекта с FastAPI

SQLModel vs SQLAlchemy: что выбрать для Python-проекта с FastAPI

$ sudo teach IT
·18 июля 2026 г.·8 мин·78
SQLModel vs SQLAlchemy: что выбрать для Python-проекта с FastAPI

Сравниваем два подхода к работе с базами данных в Python-экосистеме: где SQLModel упрощает жизнь, а где мощность SQLAlchemy незаменима. С примерами моделей, запросов и миграций.

Содержание
Что такое SQLAlchemy и почему оно до сих пор актуальноЧто такое SQLModel и какую проблему он решаетПаттерн разделения схем в SQLModelЗапросы: сравниваем подходыПростые выборкиСложные JOIN-запросыМиграции: Alembic в обоих случаяхAsync: где и как работаетКогда выбирать SQLModelКогда выбирать SQLAlchemyРеальный компромисс: SQLModel поверх SQLAlchemyСостояние проекта и рискиВывод

Когда разработчик начинает новый проект на FastAPI, один из первых архитектурных вопросов — как организовать слой работы с базой данных. Исторически ответ был очевиден: SQLAlchemy. Это зрелая, мощная и хорошо задокументированная библиотека, которая де-факто стала стандартом для Python ORM. Но в 2021 году Себастьян Рамирес — автор FastAPI и Pydantic — выпустил SQLModel, позиционируя его как более удобный способ работать с SQL в связке с FastAPI и Pydantic.

С тех пор вопрос «что выбрать» стал регулярно всплывать в сообществах. Ответ, как это обычно бывает в инженерии, зависит от контекста. Давайте разберём оба инструмента честно: без маркетинга, с примерами кода и реальными компромиссами.


Что такое SQLAlchemy и почему оно до сих пор актуально

SQLAlchemy существует с 2006 года. Это не просто ORM — это два слоя в одном пакете: Core (низкоуровневый SQL Expression Language) и ORM (высокоуровневый маппинг объектов). Версия 2.0, вышедшая в 2023 году, принесла нативную поддержку async, улучшенную типизацию и более чистый API.

Ключевые характеристики SQLAlchemy:

  • Зрелость и стабильность: 18+ лет в продакшене, огромная экосистема, поддержка всех мажорных СУБД.
  • Гибкость: можно работать как через ORM, так и напрямую через Core — строить сложные запросы, использовать raw SQL, управлять транзакциями вручную.
  • Alembic: де-факто стандарт для миграций в Python-проектах, разработанный тем же автором.
  • Async-поддержка: через AsyncSession и asyncpg/aiosqlite.

Модель на SQLAlchemy 2.0 с декларативным стилем выглядит так:

code
from sqlalchemy import String, Integer, ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(100), nullable=False)
    email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False)
    posts: Mapped[list["Post"]] = relationship(back_populates="author")

class Post(Base):
    __tablename__ = "posts"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    title: Mapped[str] = mapped_column(String(200))
    author_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
    author: Mapped["User"] = relationship(back_populates="posts")

Это уже значительно лучше, чем классический Column-синтаксис из SQLAlchemy 1.x. Типизация через Mapped работает с mypy и IDE. Но здесь нет ничего общего с Pydantic — и это ключевая точка трения при работе с FastAPI.


Что такое SQLModel и какую проблему он решает

SQLModel — это надстройка над SQLAlchemy и Pydantic одновременно. Технически это тонкая обёртка: под капотом SQLModel использует SQLAlchemy для работы с базой данных и Pydantic для валидации данных. Идея в том, чтобы один класс мог служить одновременно ORM-моделью и Pydantic-схемой.

Та же структура на SQLModel:

code
from typing import Optional
from sqlmodel import SQLModel, Field, Relationship

class User(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(max_length=100)
    email: str = Field(max_length=255, unique=True)
    posts: list["Post"] = Relationship(back_populates="author")

class Post(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    title: str = Field(max_length=200)
    author_id: Optional[int] = Field(default=None, foreign_key="user.id")
    author: Optional[User] = Relationship(back_populates="posts")

Синтаксис заметно компактнее. Но главное преимущество проявляется при интеграции с FastAPI: одну и ту же модель (или её вариации) можно использовать как схему запроса, схему ответа и ORM-модель.

Паттерн разделения схем в SQLModel

На практике рекомендуется разделять модели на несколько классов:

code
from typing import Optional
from sqlmodel import SQLModel, Field

# Базовая схема — общие поля
class UserBase(SQLModel):
    name: str
    email: str

# ORM-модель — добавляем table=True и id
class User(UserBase, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)

# Схема для создания — без id
class UserCreate(UserBase):
    password: str

# Схема для ответа API — без пароля
class UserRead(UserBase):
    id: int

Этот паттерн позволяет переиспользовать логику без дублирования. В FastAPI это выглядит так:

code
from fastapi import FastAPI, Depends
from sqlmodel import Session, select

app = FastAPI()

@app.post("/users/", response_model=UserRead)
def create_user(user: UserCreate, session: Session = Depends(get_session)):
    db_user = User.model_validate(user)
    session.add(db_user)
    session.commit()
    session.refresh(db_user)
    return db_user

@app.get("/users/{user_id}", response_model=UserRead)
def get_user(user_id: int, session: Session = Depends(get_session)):
    user = session.get(User, user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return user

Никакого ручного маппинга между ORM-объектом и Pydantic-схемой. Это реальное сокращение бойлерплейта.


Запросы: сравниваем подходы

Простые выборки

SQLAlchemy 2.0:

code
from sqlalchemy import select
from sqlalchemy.orm import Session

def get_active_users(session: Session) -> list[User]:
    stmt = select(User).where(User.is_active == True).order_by(User.name)
    return session.scalars(stmt).all()

SQLModel:

code
from sqlmodel import Session, select

def get_active_users(session: Session) -> list[User]:
    stmt = select(User).where(User.is_active == True).order_by(User.name)
    return session.exec(stmt).all()

Разница минимальна: session.scalars() vs session.exec(). SQLModel использует собственный метод exec, который возвращает типизированный результат без лишних обёрток.

Сложные JOIN-запросы

Здесь начинаются различия. SQLAlchemy Core даёт полный контроль:

code
from sqlalchemy import select, func, join

stmt = (
    select(
        User.id,
        User.name,
        func.count(Post.id).label("post_count")
    )
    .join(Post, Post.author_id == User.id, isouter=True)
    .group_by(User.id, User.name)
    .having(func.count(Post.id) > 5)
    .order_by(func.count(Post.id).desc())
)

result = session.execute(stmt).all()

SQLModel поддерживает тот же синтаксис (он наследует его от SQLAlchemy), но типизация сложных агрегатных запросов работает хуже — IDE и mypy часто не могут вывести типы для select() с несколькими колонками.


Миграции: Alembic в обоих случаях

Ни SQLModel, ни SQLAlchemy не имеют встроенного инструмента миграций — оба используют Alembic. Разница в настройке.

Для SQLAlchemy env.py выглядит стандартно:

code
from myapp.models import Base
target_metadata = Base.metadata

Для SQLModel нужно импортировать все модели явно, чтобы они зарегистрировались в метаданных:

code
from sqlmodel import SQLModel
# Важно: импортировать все модели до использования metadata
from myapp.models import User, Post  # noqa: F401

target_metadata = SQLModel.metadata

Типичная ошибка при работе с SQLModel и Alembic — забыть импортировать модели. В этом случае autogenerate не увидит таблицы и сгенерирует пустую миграцию. SQLAlchemy в этом плане ведёт себя аналогично, но документации и примеров для него значительно больше.


Async: где и как работает

Обе библиотеки поддерживают асинхронный режим, но SQLModel здесь отстаёт.

Async с SQLAlchemy:

code
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker

engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)

async def get_users(session: AsyncSession) -> list[User]:
    result = await session.execute(select(User))
    return result.scalars().all()

SQLModel официально поддерживает async начиная с версии 0.0.14, но документация по этой теме остаётся скудной. На практике используют AsyncSession из SQLAlchemy напрямую:

code
from sqlalchemy.ext.asyncio import AsyncSession
from sqlmodel import select

async def get_users(session: AsyncSession) -> list[User]:
    result = await session.exec(select(User))  # exec не работает с AsyncSession!
    return result.all()

Здесь кроется подводный камень: метод exec() из SQLModel не совместим с AsyncSession из SQLAlchemy. Приходится использовать execute() и вручную вызывать .scalars(). Это нарушает одно из ключевых удобств SQLModel.

Тем, кто хочет детально разобраться с этими нюансами и построить правильную архитектуру с нуля, может пригодиться курс по SQLModel — там разбираются именно такие практические сценарии, включая async и интеграцию с FastAPI.


Когда выбирать SQLModel

SQLModel оправдан в следующих сценариях:

Новый проект с FastAPI без сложной логики БД. Если приложение — это в основном CRUD с несколькими таблицами, SQLModel даёт ощутимый выигрыш в скорости разработки. Меньше кода, меньше мест для ошибок, встроенная валидация.

Команда с опытом в Pydantic, но без глубокого знания SQLAlchemy. Синтаксис SQLModel ближе к Pydantic, порог входа ниже.

Прототипирование и MVP. Быстро поднять API с базой данных — SQLModel справляется отлично.

Монолитные API без сложных аналитических запросов. Если нет оконных функций, рекурсивных CTE и хитрых JOIN-стратегий, SQLModel не будет ограничивать.


Когда выбирать SQLAlchemy

Сложные запросы и аналитика. Оконные функции, CTE, сложные агрегации — SQLAlchemy Core даёт полный контроль над генерируемым SQL.

Унаследованные проекты. Если база данных уже существует и имеет нестандартную схему, SQLAlchemy гибче справляется с маппингом.

Большие команды с разными уровнями опыта. Документации, туториалов и Stack Overflow-ответов для SQLAlchemy на порядок больше.

Требования к производительности. Тонкая настройка стратегий загрузки (lazy, selectin, joined), управление пулом соединений, оптимизация N+1 — всё это в SQLAlchemy задокументировано и проверено годами.

Async как основной режим работы. Async SQLAlchemy зрелее, лучше задокументирован и не имеет описанных выше несовместимостей.


Реальный компромисс: SQLModel поверх SQLAlchemy

Важно понимать: SQLModel не конкурирует с SQLAlchemy — он строится на его основе. Это означает, что при необходимости можно использовать SQLAlchemy-конструкции напрямую внутри SQLModel-проекта.

code
from sqlmodel import SQLModel, Field, Session
from sqlalchemy import text

class User(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str

# Когда нужен сырой SQL или сложный запрос — используем SQLAlchemy напрямую
def get_users_raw(session: Session):
    result = session.execute(text("SELECT id, name FROM user WHERE is_active = true"))
    return result.fetchall()

Это позволяет начать с SQLModel для простых сущностей и постепенно переходить к SQLAlchemy там, где нужна мощность.


Состояние проекта и риски

Честный разговор о SQLModel невозможен без упоминания его темпа развития. На момент написания статьи проект развивается медленнее, чем ожидало сообщество: issues накапливаются, некоторые баги существуют годами. SQLAlchemy в этом плане значительно надёжнее — за ним стоит коммерческая поддержка (Zope Foundation и спонсоры) и многолетняя история.

Если проект критичен для бизнеса и требует долгосрочной поддержки — это аргумент в пользу SQLAlchemy. Если это внутренний инструмент или стартап, где скорость важнее — SQLModel вполне приемлем.


Вывод

SQLModel и SQLAlchemy решают одну задачу разными средствами. SQLModel выигрывает в скорости разработки и интеграции с FastAPI/Pydantic — особенно для простых и средних по сложности приложений. SQLAlchemy выигрывает в зрелости, гибкости и полноте контроля над слоем данных.

Практическая рекомендация: начинайте с SQLModel, если строите FastAPI-приложение с нуля и не предвидите сложных запросов. Используйте SQLAlchemy, если проект долгосрочный, команда большая или требования к базе данных нетривиальны. И помните, что SQLModel не запрещает использовать SQLAlchemy там, где это нужно — это не выбор «или/или».

Если хочется разобраться с SQLModel системно — от базовых моделей до async и миграций — стоит посмотреть на курс по SQLModel: там эти темы разобраны последовательно и с практическими примерами. Но и официальная документация SQLAlchemy 2.0 сейчас заметно улучшилась и остаётся одним из лучших технических ресурсов в Python-экосистеме.

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

Автор

$ sudo teach IT

SQLModel

Курс по теме

SQLModel

С нуля до уверенной работы с базами данных на Python. Подходит для начинающих и тех, кто уже пишет на Python. Разберитесь с SQLModel раз и навсегда — и перестаньте бояться SQL и таблиц.

Открыть курс

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Простыми словами о защищенном соединении

Простыми словами о защищенном соединении

Базовый курс для обычных людей: что такое защищённое соединение, как оно устроено, что оно умеет и чего не умеет, как выбрать сервис и настроить его на своих устройствах.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Сервис, который не падает - как построить безопасную зашифрованную сеть

Сервис, который не падает - как построить безопасную зашифрованную сеть

Практический курс по безопасности сети: как собрать собственный сервис защищённого доступа - от пустого сервера до подписки с автовыбором - и, что важнее, как потом не уронить его своими руками.

Перейти к курсу

Другие статьи

Python для аналитики на практике: как строить признаки и проверять их воспроизводимость
python

Python для аналитики на практике: как строить признаки и проверять их воспроизводимость

Поймём, где чаще всего ломается воспроизводимость (версии данных, фильтры, дрейф) и как оформить пайплайн признаков так, чтобы результаты совпадали между запусками и окружениями.

17 сентября 2026 г.
1050
Запуск и поддержка проекта на Ruff в команде: формат “как в CI”, а не “как договорились”
запуск

Запуск и поддержка проекта на Ruff в команде: формат “как в CI”, а не “как договорились”

Как правильно выстроить конфигурацию Ruff (линтинг, форматирование, автозамены) и сделать так, чтобы одинаковые правила гарантировались в локальной сборке и на CI без ручного шаманства.

16 сентября 2026 г.
1050
Создание Telegram бота в 2026 легко и просто! Полный курсы!
создание

Создание Telegram бота в 2026 легко и просто! Полный курсы!

19 июня 2026 г.
1431
Как выбрать, куда идти в IT: веб, боты, данные — без хаоса
выбрать

Как выбрать, куда идти в IT: веб, боты, данные — без хаоса

Разберём самые понятные направления для новичка (веб, боты, аналитика/данные) и чем они отличаются по шагам обучения. В конце вы получите простой план, как проверить, что вам подходит, и какой стартовый путь выбрать.

5 октября 2026 г.
580
Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть
путь

Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть

Разберём структуру репозитория, как упаковать проект в понятную историю (проблема → решение → запуск → метрики качества) и какие артефакты повышают доверие: скринкасты, архитектурная схема, чек-лист продакшн-готовности.

17 сентября 2026 г.
1090
Как понять, какой язык программирования учить первым: простой тест и выбор
понять

Как понять, какой язык программирования учить первым: простой тест и выбор

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

2 октября 2026 г.
630

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

Что такое SQLAlchemy и почему оно до сих пор актуальноЧто такое SQLModel и какую проблему он решаетПаттерн разделения схем в SQLModelЗапросы: сравниваем подходыПростые выборкиСложные JOIN-запросыМиграции: Alembic в обоих случаяхAsync: где и как работаетКогда выбирать SQLModelКогда выбирать SQLAlchemyРеальный компромисс: SQLModel поверх SQLAlchemyСостояние проекта и рискиВывод