rzaripov.kz

Почему я предпочитаю сырые SQL-запросы в PHP

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

В моей практике PHP обслуживает API для настольных и мобильных клиентов, разработанных с использованием Delphi и FireMonkey, а также Kotlin и Swift. В таком контексте ценны согласованные контракты, понятная логика доступа к данным и быстрый поиск источника ошибки. Поэтому ORM я воспринимаю как полезный инструмент, а не обязательный фундамент приложения. Сырой SQL дает мне контроль, который не всегда обеспечивает ORM.

Сравнение подходов

ORM хорошо закрывает типовые сценарии: создание объекта, чтение списка, обновление полей и удаление записи. Сырой SQL требует больше кода, зато позволяет задать конкретные поля, порядок, условия и форму результата. Помимо синтаксиса, важно, кто принимает решения о выполнении запросов.

Критерий ORM Сырые SQL-запросы
Скорость разработки Высокая для стандартных CRUD-сценариев Средняя: запросы и служебный код пишутся явно
Контроль над запросом Ограничен возможностями ORM Полный
Поведение под нагрузкой Зависит от настроек и выбранной стратегии Определяется разработчиком и схемой
Перенос между базами Часто требует переработки запросов Ограничен диалектом SQL
Отладка Удобна при знании внутренностей ORM Удобна при доступе к SQL и плану запроса

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

ORM как слой абстракции

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

Особенно настораживает N+1: после выборки списка ORM лениво загружает связанные сущности и выполняет дополнительный запрос для каждой записи. Жадная загрузка и правильные индексы способны исправить проблему, но требуют знания внутреннего поведения. Явный JOIN сразу показывает объем работы. В логах рабочего окружения такой сценарий иногда заметен лучше, чем в коде, хотя требует внимания к формату вывода.

Производительность и нагрузка

ORM не обязательно медленнее прямых запросов. На небольших объемах разница по времени ответа обычно невелика, хотя ORM добавляет создание объектов и служебные вызовы. Скорость определяется индексом, планом запроса, числом обращений и объемом переносимых данных. Поэтому вместо общих утверждений я смотрю EXPLAIN, время ответа и статистику запросов на реальных данных.

Типичная ловушка возникает, когда удобный ORM-метод превращает одну операцию в десятки обращений. Например, вывод двадцати заказов с позициями может породить двадцать один запрос вместо одного JOIN. Явный SQL с LIMIT, индексированными полями и пагинацией позволяет заранее оценить объем работы. ORM можно оптимизировать, но тогда часть знаний о базе все равно возвращается в код приложения.

Безопасность SQL-запросов

Сырой SQL небезопасен сам по себе: склейка значения из формы с текстом запроса открывает путь к SQL-инъекции. Безопасный вариант — готовить запрос через PDO, передавать значения параметрами и отдельно проверять имена таблиц и столбцов. Параметры защищают значения, но не имена, поэтому динамические идентификаторы выбирают из заранее заданного списка. ORM обычно применяет тот же принцип, если сама не подставляет пользовательские фрагменты.

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

Читаемость и сопровождение

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

Отказ от ORM не означает свалку запросов в контроллерах. Я размещаю их в репозиториях или сервисах доступа к данным, задаю единые имена, возвращаю типизированные DTO и провожу изменения небольшими коммитами. Так сохраняются границы модулей и удобство повторного использования. ORM способен убрать эту рутину, но вместе с ней исчезает и контроль над тем, какой запрос фактически уходит в PostgreSQL, MySQL или SQLite.

SQL в клиент-серверных приложениях

В моих проектах серверная часть на PHP часто взаимодействует с клиентами на Delphi, FireMonkey, Kotlin и Swift. Клиенту не нужен прямой доступ к базе: он получает версионированные ответы API, а SQL-запросы остаются внутренним механизмом сервиса. Такое разделение позволяет менять структуру хранилища, не затрагивая мобильный интерфейс, и сохранять правила валидации в одном месте.

Практический пример такого взаимодействия описан в материале REST-клиент на Delphi: клиент вызывает PHP-методы, обменивается данными через REST и не знает о соединениях с базой. Кроссплатформенному клиенту нужны стабильные форматы ответа, коды ошибок и правила пагинации, а не особенности ORM. На сервере эти требования удобно реализовывать поверх явных запросов, не связывая внешний контракт с внутренней моделью сущностей.

Границы разумного компромисса

ORM вполне уместна там, где сущности простые, операции повторяются, а команда ценит скорость разработки. Административные панели, прототипы и сервисы с CRUD-логикой могут получить пользу от готовых миграций и удобной работы со связями. Мой критерий прост: если библиотека делает код понятнее и не создает скрытых запросов, я не буду из принципа отказываться от нее ради личного предпочтения.

В небольшой команде важны общие соглашения и прозрачность решений. Материал о парном программировании убедительно показывает, как совместная работа помогает сформировать единый стиль и быстрее заметить неочевидный код. Применительно к SQL это означает обсуждать структуру запросов на ревью, заранее договариваться об именах и проверять план выполнения до слияния изменений. Такой процесс снижает риск не столько опечаток, сколько неверно выбранной стратегии доступа к данным.

Поэтому мой рабочий процесс ближе к гибридному. Миграции и простые операции можно поручить ORM, сложные выборки и пакетные изменения выполнять параметризованными запросами через PDO, а транзакции контролировать явно. Код должен объяснять, какие данные изменяются и почему; план запроса и журналирование помогают проверять предположения. Я предпочитаю сырой SQL не как более модный инструмент, а как способ сохранить ясность, контроль и ответственность разработчика за результат.