Что быстрее выполнять: ORM или сырые SQL запросы и почему
Что проверяют
Понимаете ли вы, где именно тратится время. Заученный ответ «сырой SQL быстрее» слышно сразу, и следом идёт вопрос, за счёт чего, на котором разговор обычно и заканчивается.
Как отвечать
Начать с главного: ORM не альтернатива SQL, он его порождает. Если текст запроса одинаковый, база построит тот же план и выполнит за то же время, значит разница лежит в двух других местах.
Первое: какой SQL сгенерирован. Здесь и живут настоящие потери, и они не про проценты, а про порядки: N+1, когда на список делается один запрос плюс по запросу на элемент; выборка всех колонок, когда нужны две; построчные вставки вместо пакетной.
Второе: стоимость Python поверх ответа. Сборка запроса, разбор строк, создание объекта на каждую запись, отслеживание изменений в сессии. На десятках строк это незаметно, на сотнях тысяч становится основной статьёй расхода.
Отсюда практический вывод: сначала посмотреть сгенерированный SQL и план, потом лечить средствами ORM, select_related и prefetch_related, only и values, bulk_create и iterator, в SQLAlchemy joinedload и selectinload либо уровень Core без создания сущностей. Сырой SQL оставить там, где ORM не выражает запрос: оконные функции, рекурсивные CTE, массовый UPSERT, тяжёлая аналитика.
Где спотыкаются
Сравнивают ORM и SQL как две разные технологии доступа к данным, будто ORM ходит в базу как-то иначе. Из этого потом растёт вера, что переписывание запроса руками само по себе ускорит, и переписанный код оказывается ровно тем же планом.
Второе: уходят в сырой SQL вместо профилирования и воспроизводят те же проблемы вручную, только теперь без единой точки, где их видно. Цикл с запросом внутри остаётся циклом с запросом внутри.
Третье: собирают сырой запрос конкатенацией или f-строкой и открывают инъекцию. Значения передают параметрами, подставлять в текст можно только имена объектов, и то из белого списка.