Как реализовать систему тегов, аналогичную SO в php / mysql? - PullRequest
1 голос
/ 07 октября 2009

Я кодирую сайт на PHP / MySQL, и я хотел бы реализовать аналогичный механизм тегирования stackoverflow. У меня есть 3 соответствующие таблицы в БД: 1. Предметы 2. Теги 3. ItemTagMap (отображает теги на элементы, отображение n: n)

Теперь на странице поиска я хотел бы показать отдельный список всех тегов для всего результата поиска (не только для текущей страницы), чтобы пользователи могли «уточнить» свой поиск, добавляя / удаляя теги из этого списка тегов.

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

Кто-нибудь знает, как это эффективно реализовать?

Ответы [ 3 ]

8 голосов
/ 07 октября 2009

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

SELECT T.Tagid, TagInfo.TagName,  COUNT(*)
FROM Items I
JOIN Tags TagInfo ON TagInfo.TagId = T.TagId
JOIN ItemTagMap T  ON I.ItemId = T.ItemId 
--JOIN ItemTagMap T1 ON I.ItemId = T1.ItemId
WHERE I.ItemId IN
  (
      SELECT ItemId 
      FROM Items
      WHERE   -- Some typical initial search criteria
         Title LIKE 'Bug Report%'   -- Or some fulltext filter instead...
         AND  ItemDate > '02/22/2008'
         AND  Status = 'C'
  )
--AND T1.TagId = 'MySql'
GROUP BY T.TagId, TagInfo.TagName
ORDER BY COUNT(*) DESC

Подзапрос - это «запрос на вождение», то есть тот, который соответствует начальным критериям конечного пользователя. (подробности о том, как этот запрос, требуемый несколько раз, может вписаться в общий оптимизированный поток, см. ниже) Прокомментировано соединение на T1 (и, возможно, T2, T3, когда выбрано несколько тегов), и, с предложением WHERE, соответствующие критерии. Они необходимы, когда пользователь выбирает определенный тег, как в рамках начального поиска, так и путем уточнения. (Это может быть более эффективным, чтобы разместить эти объединения и где предложения внутри подзапроса; подробнее об этом ниже)

Обсуждение ... «Управляющий запрос» или его разновидность необходимы для двух разных целей:

  • 1 для предоставления полного списка ItemId, который необходим для перечисления всех связанных тегов.

  • 2 для предоставления первых N значений ItemId (N - размер отображаемой страницы) для поиска подробной информации об элементе в таблице Item.

Обратите внимание, что полный список не нужно сортировать (или он может выиграть от сортировки в другом порядке), поэтому второй список нужно сортировать по выбору пользователя (скажем, по дате, по убыванию или по названию). в алфавитном порядке по возрастанию). Также обратите внимание, что если требуется какой-либо порядок сортировки, стоимость запроса будет подразумевать работу с полным списком (не допускает нечетной оптимизации самим SQL и / или некоторой денормализации, SQL должен «увидеть» последние записи в этом списке). в случае, если они принадлежат к вершине, в порядке сортировки).

Этот последний факт говорит о том, что для обеих целей используется один и тот же запрос, соответствующий список может быть сохранен во временной таблице. Общий процесс будет состоять в том, чтобы быстро найти верхние записи N Item с их деталями и сразу же вернуть их в приложение. Затем приложение может получить ajax-fashion список тегов для уточнений. Этот список будет создан с запросом, похожим на приведенный выше, где подзапрос заменяется на «select * from временная таблица». Хорошие шансы, что оптимизатор SQL решит отсортировать этот список (в некоторых случаях), давайте позволим ему это сделать, вместо того, чтобы угадывать его и сортировать его явно.

Еще один момент, который следует рассмотреть, - это, возможно, включить объединение (я) в таблицу ItemTagMap в «запрос на вождение», а не так, как показано выше. Вероятно, это лучше всего сделать как с точки зрения производительности, так и потому, что он создаст правильный список для цели № 2 (отображение страницы элементов).

Запрос / поток, описанный выше, вероятно, будет достаточно хорошо масштабироваться даже на относительно скромном оборудовании; ориентировочно на 1/2 миллиона + предметов, при длительном поиске пользователей может быть до 10 в секунду. Одним из ключевых факторов будет селективность начальных критериев поиска.

Идеи оптимизации

  • [В зависимости от типичных случаев поиска и статистики данных] может иметь смысл денормализовать путем переноса (даже дублирования) некоторых полей элементов в таблицу ItemTagMap. Короткие поля, в частности, могут быть там «желанными».
  • Поскольку данные растут в миллионах элементов, мы можем использовать типично сильную корреляцию некоторых тегов (например, в SO PHP часто поставляется с MySql, кстати, без веской причины ...), с различными приемами. Например, введение тегов «multi-Tag» может сделать логику ввода более сложной, но также может значительно уменьшить размер карты.


- грубо сказал! -
Соответствующая архитектура и оптимизации должны быть выбраны с учетом фактических требований и эффективного статистического профиля данных ...

0 голосов
/ 07 октября 2009

Вы можете попытаться свести к минимуму количество вызовов БД, вкладывая тяжелую работу в PHP.

Сначала выберите все ваши предметы из БД:

select * from items where (conditions);

Затем создайте массив всех идентификаторов из набора результатов.

$ids = array();
foreach ($items as $item) {
    $ids[] = $item['id'];
}
$ids = implode(',' $ids);

Затем выберите все ItemTagMaps и связанные данные тега для идентификаторов элементов, которые вы ранее получили.

select map.item_id, t.id, t.name from tags t, item_tag_maps map where t.id = map.tag_id and map.item_id in ($ids);

Теперь, когда вы перебираете свой массив $ items, вы можете найти все совпадающие теги из второго выполненного вами запроса SQL, если у него совпадает значение item_id.

0 голосов
/ 07 октября 2009

Предполагая, что:

  • Item (id);
  • тег (id, name) с указателем на имени;
  • ItemTag (item_id, tag_id).

тогда:

SELECT t.name
FROM Tag t
WHERE EXISTS (SELECT 1 FROM ItemTag WHERE item_id = 1234)
ORDER BY t.name

Ничего интенсивного в этом нет. Это похоже, но я думаю, что это будет медленнее:

SELECT t.name
FROM Tag t
WHERE t.id IN (SELECT tag_id FROM ItemTag WHERE item_id = 1234)
ORDER BY t.name

Это также можно сделать как объединение:

SELECT DISTINCT t.name
FROM Tag t
JOIN ItemTag i WHERE i.tag_id = t.id
WHERE i.item_id = 1234
ORDER BY t.name

Я думаю, что первый будет быстрее, но, как всегда в случае с SQL, его стоит протестировать (для набора данных достаточного размера).

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

...