Плюсы и минусы написания запросов с ORM и на SQL
SQL против ORM — один из самых горячих споров среди разработчиков. Одни уверены, что писать SQL-запросы вручную — это гарантия контроля и эффективности. Другие считают, что ORM упрощает жизнь и снижает вероятность ошибок. А что, если правда где-то посередине?
В этой статье я разберу плюсы и минусы обоих подходов, рассмотрю реальные примеры уязвимостей, производительности и удобства сопровождения кода. Почему SQL-инъекции до сих пор остаются проблемой? Когда ORM становится тормозом, а когда — спасением? Правда ли, что ORM — это просто «модная игрушка», или же он стал стандартом разработки? Давайте разберемся, а заодно — подискутируем в комментариях.
Содержание
Проблемы с SQL-запросами
Преимущества ORM
Ошибки при работе с ORM
Когда стоит использовать чистый SQL?
Комбинирование ORM и SQL
Как проверять, какие SQL-запросы генерирует ORM?
Тренды: уходит ли ORM в прошлое?
Выводы
Проблемы с 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 там, где это необходимо. Главное — изучить оба инструмента и выбирать их осознанно!
Вопросы для обсуждения:
В каких случаях вы предпочли бы SQL вместо ORM?
Как вы решаете проблемы производительности ORM?
Были ли у вас случаи, когда переписывали проект с ORM на SQL (или наоборот)?
Пишите в комментариях — обсудим!