Плюсы и минусы написания запросов с ORM и на SQL

4f025ed5c6c03294960d5cc10350437c

SQL против ORM — один из самых горячих споров среди разработчиков. Одни уверены, что писать SQL-запросы вручную — это гарантия контроля и эффективности. Другие считают, что ORM упрощает жизнь и снижает вероятность ошибок. А что, если правда где-то посередине?

В этой статье я разберу плюсы и минусы обоих подходов, рассмотрю реальные примеры уязвимостей, производительности и удобства сопровождения кода. Почему SQL-инъекции до сих пор остаются проблемой? Когда ORM становится тормозом, а когда — спасением? Правда ли, что ORM — это просто «модная игрушка», или же он стал стандартом разработки? Давайте разберемся, а заодно — подискутируем в комментариях.

Содержание

  1. Проблемы с SQL-запросами

  2. Преимущества ORM

  3. Ошибки при работе с ORM

  4. Когда стоит использовать чистый SQL?

  5. Комбинирование ORM и SQL

  6. Как проверять, какие SQL-запросы генерирует ORM?

  7. Тренды: уходит ли ORM в прошлое?

  8. Выводы

Проблемы с SQL-запросами

SQL-инъекции

Рассмотрим простой SQL-запрос:

def get_user(conn, user_id):
    return conn.execute(
        f"""
        SELECT * FROM users WHERE id = {user_id}
        """
    )

В чем проблема? Этот код уязвим для SQL-инъекции. Если user_id содержит вредоносный SQL-код, например:

1; DROP TABLE users;

То запрос превратится в:

SELECT * FROM users WHERE id = 1; DROP TABLE users;

В результате можно потерять всю таблицу!

Конечно тут дело не в том, что мы используем SQL, а просто ошибка разработчика, который неверно передал параметр в запрос.

Решение — использовать плейсхолдеры:

def get_user(conn, user_id):
    return conn.execute(
        "SELECT * FROM users WHERE id = ?",
        (user_id, )
    )

Сложность формирования SQL-запросов

Допустим, у нас есть фильтрация по нескольким параметрам:

def get_books(conn, name, author):
    query = "SELECT * FROM books"
    where = []
    if name:
        where.append(f"name = '{name}'")
    if author:
        where.append(f"author = '{author}'")
    if where:
        query += " WHERE " + " AND ".join(where)
    return conn.execute(query)

Здесь сразу две потенциальные проблемы:

  • SQL-инъекции.

  • Сложность формирования строки запроса.

Чем больше условий, тем сложнее читать и поддерживать код.

Преимущества ORM

Безопасность

ORM автоматически подставляет параметры запросов, снижая риск SQL-инъекций:

def get_user(user_id):
    return User.objects.filter(id=user_id).first()

Удобство написания запросов

Пример выборки книг с ORM:

def get_books(name=None, author=None):
    query = Book.objects.all()
    if name:
        query = query.filter(name=name)
    if author:
        query = query.filter(author=author)
    return query

Запрос читается проще, а ORM сам оптимизирует его под конкретную БД.

Автоматизация миграций

Django и SQLAlchemy поддерживают автоматическое создание миграций, упрощая управление схемой БД.

Low-code возможности

ORM позволяет быстро создавать админ-панели, например, Django Admin или Flask-Admin.

Ошибки при работе с ORM

Не всегда ORM-запросы оптимальны. Например:

users = User.objects.all()
for user in users:
    print(user.profile)

Это вызовет проблему Query N+1. Нужно использовать select_related:

users = User.objects.select_related("profile").all()

Чтобы эффективно использовать ORM, нужно хорошо понимать как он работает. Нет смысла просто ругать ORM. Использование ORM не отменяет того, что вам нужно также отлично знать SQL и внутреннее устройство БД.

Многие начинающие ошибочно думают, что ORM — это абстракция, которая позволяет не изучать SQL. Это не так.

Когда стоит использовать чистый SQL?

Иногда SQL предпочтительнее:

  • Для сложных запросов, которые ORM не поддерживает или это сервис без миграций и админки, а предназначен только для выполнения больших сложных запросов например, для формирования отчетов.

  • Если ORM генерирует неэффективные запросы и не поддерживает нужные SQL-конструкции.

  • Когда нужно работать с нестандартными SQL-конструкциями.

Комбинирование ORM и SQL

Чистая архитектура

Хорошая практика — использовать слой репозитория, где SQL и ORM-методы сосуществуют.

Возвращать из методов следует DTO-объекты, а не ORM-модели или словари, тогда код будет чистым.

Использование ORM для миграций и админки

Обычно даже в проектах с SQL используют ORM как минимум для миграций и админки.

Как проверять, какие SQL-запросы генерирует ORM?

Django ORM:

В Django можно увидеть, какой SQL-запрос будет выполнен, через свойство query:

print(str(Book.objects.filter(name='Django').query))

Также можно включить логирование SQL-запросов.

Если речь о миграциях, можно посмотреть, какой SQL сгенерировала Django ORM:

./manage.py sqlmigrate app_name 0005

Это покажет SQL-код миграции и поможет убедиться, что ORM корректно изменяет схему базы. Иногда можно увидеть неожиданное поведение поэтому, все миграции и запросы всегда нужно проверять.

SQLAlchemy ORM:

Чтобы включить логирование всех запросов в SQLAlchemy, можно при создании engine передать флаг echo=True:

from sqlalchemy import create_engine

engine = create_engine("sqlite:///example.db", echo=True)

Тренды: уходит ли ORM в прошлое?

Некоторые разработчики считают, что ORM теряет популярность, но на самом деле он продолжает широко использоваться. ORM предлагает автоматическое управление схемой БД, генерацию миграций и интеграцию с административными панелями.

Часто отказ от ORM происходит из-за незнания его возможностей и особенностей. Опытные разработчики, хорошо разбирающиеся в ORM, редко переходят на чистый SQL без веских причин, но и сам SQL знают хорошо. Или как минимум используют ORM для миграций и админ-панелей.

Выводы

  • ORM облегчает работу с базой, делая код более читаемым и безопасным и предоставляет LOW-CODE решение для админ-панели.

  • Чистый SQL нужен в сложных запросах и при оптимизации производительности там, где ORM не поддерживает нужные конструкции.

  • Важно следить за тем, какие SQL-запросы генерирует ORM, используя инструменты отладки и логирования.

  • Оптимальный вариант — совмещать оба подхода: использовать ORM для удобства и LOW-CODE админ-панели, а SQL там, где ORM неэффективен.

  • Если сомневаетесь, выбирайте ORM — он безопаснее и удобнее.

Хороший разработчик должен хорошо знать и SQL, и ORM. Нельзя даже объективно рассуждать, что лучше, если вы хорошо знаете только что-то одно.

Стоит ли использовать ORM или писать SQL-запросы вручную? Ответ зависит от контекста. В большинстве случаев ORM ускоряет разработку, уменьшает вероятность ошибок и улучшает читаемость кода. Однако для сложных или специфичных запросов SQL остается незаменимым.

Лучший вариант — комбинировать оба подхода: использовать ORM для удобства и SQL там, где это необходимо. Главное — изучить оба инструмента и выбирать их осознанно!

Вопросы для обсуждения:

  1. В каких случаях вы предпочли бы SQL вместо ORM?

  2. Как вы решаете проблемы производительности ORM?

  3. Были ли у вас случаи, когда переписывали проект с ORM на SQL (или наоборот)?

Пишите в комментариях — обсудим!

© Habrahabr.ru