Уменьшение количества запросов в MySQL с множеством отношений «один ко многим» (ORM) - PullRequest
4 голосов
/ 04 ноября 2010

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

Настройка
Например, есть страница, на которой вы можете просмотреть категорию, полную разделов, которые, в свою очередь, полны продуктов. Это перечислено в табличном формате. Каждый продукт имеет (возможно) множество атрибутов, флагов, ценовых льгот. Все это должно быть представлено в таблице.

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

Пока ...
Все ключи проиндексированы. Я могу получить всю информацию одним запросом (см. Редактирование ниже), однако, как вы можете себе представить, это приведет к тому, что множество избыточных данных будет разбросано по нескольким строкам для каждого продукта, для каждого дополнительного (например) атрибута, флаг и т. д.

Я не против отказа от ORM для отображаемой части приложения и для построения запросов или даже необработанного SQL.

Решение для этого может быть довольно простым, и я просто не знаю об этом прямо сейчас, что, честно говоря, было бы облегчением. Или, может быть, это не так. Я не уверен. Если какое-либо из моих объяснений было недостаточно адекватным, чтобы понять проблему, просто спросите, и я постараюсь привести лучший пример. (Правка: приведен лучший пример, см. Ниже

Хотя, примечание ... Одна вещь, которая может иметь какое-то значение: хотя я всегда хочу, чтобы приложение разрабатывалось наиболее эффективно, это не тот сайт, который будет посещаться десятки или сотни раз в день. Это скорее административное приложение, которое, вероятно, не будет использоваться несколькими людьми одновременно. Я не могу предвидеть слишком много перезагрузки, так как большая часть редактирования данных на странице выполняется через AJAX. Итак, должно ли меня волновать, что на этой странице каждый раз при загрузке этой конкретной страницы выполняется пара сотен запросов (в зависимости от того, сколько продуктов находится в просматриваемом разделе)? Просто побочная мысль, даже если бы можно было решить основную вышеупомянутую проблему, я бы предпочел это.

Большое спасибо!

EDIT
Судя по паре ответов, я, кажется, не объяснил себя адекватно. Итак, позвольте мне опубликовать пример, чтобы вы увидели, что происходит. Перед примером, однако, я также должен сделать два пояснения: (1) есть также пара отношений «многие ко многим», (2) и вы можете сравнить то, что я ищу, с запросом кросс-таблицы.

Давайте упростим и скажем, что у нас есть 3 основные таблицы: продукты (product_id, product_name, product_date_added) product_attributes (product_attribute_id, product_id, value) уведомления (уведомление_id, уведомление_label)

и 1 опорный тальбе: уведомления_продукта (уведомление_ид, идентификатор_продукта)

Мы собираемся перечислить все продукты в таблице. В ORM достаточно просто вызвать все продукты. Таким образом, для каждого «продукта» мы перечисляем product_name и product_date_added. Однако нам также необходимо перечислить все атрибуты продуктов. Есть 0 или более из них на продукт. Мы также должны показать, какие уведомления есть у продукта, а также 0 или более. Итак, на данный момент, как это работает в основном:

foreach ($products->find_all() as $product) //given that $products is an ORM object
{
   echo $product->product_id; //lets just pretend these are surrounded by html
   echo $product->product_name;
   foreach ($products->product_attributes->find_all() as $attribute)
   {
       echo $attribute->value;
   }
   foreach ($products->notifications->find_all() as $notification)
   {
       echo $notification->notification_label; 
   }
 }

Это, конечно, упрощенно, но об этом я и говорю.Это прекрасно работает . Однако , как вы можете видеть, для каждого продукта он должен запросить все его атрибуты, чтобы получить соответствующую коллекцию или строки.Функция find_all () будет возвращать результаты запроса чего-либо в соответствии с: SELECT product_attributes.* FROM product_attributes WHERE product_id = '#', и аналогично для уведомлений.И он выполняет эти запросы для каждого продукта.
Таким образом, для каждого продукта в базе данных количество запросов в несколько раз превышает эту сумму. Итак, хотя это работает хорошо, оно плохо масштабируется, поскольку потенциально может привести к сотням запросов.

Если я выполню запрос, чтобы собрать все данные в одном запросе, вдольстроки:

SELECT p.*, pa.*, n.*
FROM products p
LEFT JOIN product_attributes pa ON pa.product_id = p.product_id
LEFT JOIN product_notifications pn ON pn.product_id = p.product_id
LEFT JOIN notifications n ON n.notification_id = pn.notification_id

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

Например, если у меня есть два продукта в базе данных;один имеет 1 атрибут и 1 флаг, а другой имеет 3 атрибута и 2 флага, он вернет:

product_id, product_name, product_date_added, product_attribute_id, value, notification_id, notification_label
1, My Product, 10/10/10, 1, Color: Red, 1, Add This Product
2, Busy Product, 10/11/10, 2, Color: Blue, 1, Add This Product
2, Busy Product, 10/11/10, 2, Color: Blue, 2, Update This Product
2, Busy Product, 10/11/10, 3, Style: New, 1, Add This Product
2, Busy Product, 10/11/10, 3, Style: New, 2, Update This Product

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

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

Извините, что так долго пытался быть тщательным, ха-ха, спасибо!

Ответы [ 2 ]

1 голос
/ 04 ноября 2010

Интересной альтернативой является обработка ваших операций чтения и записи с использованием совершенно разных моделей.Разделение запросов команд.Сложные объектные модели (и ORMS) отлично подходят для моделирования сложного делового поведения, но они являются паршивыми интерфейсами для запросов и отображения информации для пользователей.Вы упомянули, что вы не против отказа от ORM для рендеринга дисплеев - ну, это именно то, что сейчас предлагают многие архитекторы программного обеспечения.Создать совершенно другой интерфейс (с собственными оптимизированными запросами) для чтения и составления отчетов по данным.Модель «чтения» может запрашивать ту же базу данных, которую вы используете с вашей моделью «записи» с поддержкой ORM, или она может быть отдельной, которая денормализована и оптимизирована для отчетов / экранов, которые вам нужно сгенерировать.

Проверьте эти две презентации.Это может звучать как излишнее (и может быть, если ваши требования к производительности очень низки), но удивительно, как эта техника заставляет так много проблем просто исчезать.

0 голосов
/ 04 ноября 2010

Хороший ORM должен справиться с этим для вас. Если вы чувствуете, что должны сделать это вручную, вы можете сделать это.

Получите все нужные категории в одном запросе и сохраните идентификаторы первичного ключа в массиве PHP.

Запустите запрос, подобный этому:

mysql_query('SELECT yourListOfFieldsHere FROM Products WHERE Product_id IN ('.implode(',', $categoryIDs).')');

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

...