Я мог бы дать лучший ответ, если бы вы также предоставили некоторые данные, в частности, некоторые примеры ключевых слов и примеры title
s из каждой из ваших таблиц, чтобы мы могли получить представление о том, что вы на самом деле пытаетесь найти. на. Но я постараюсь ответить тем, что вы предоставили.
Сначала позвольте мне изложить на английском языке то, что, по моему мнению, будет делать ваш запрос, а затем я объясню причины и способы его устранения.
Perform a full table scan of all instances of `exercises`
For each row in `exercises`
Find all categories attached via exerciseCategories
For each combination of exercise and category
Perform a full table scan of all instances of exerciseCategories
Look up corresponding category
Perform RLIKE match on title
Perform a full table scan of all instances of exerciseSearchtags
Look up corresponding searchtag
Perform RLIKE match on title
Join back to exercises table to re-lookup self
Perform RLIKE match on title
При условии, что у вас есть хотя бы несколько вменяемых индексов, получится E x C x (C + S + 1)
, где E
- количество упражнений, C
- среднее количество категорий для данного упражнения, а S
среднее количество поисковых тегов для данного. Если у вас нет индексов хотя бы для тех идентификаторов, которые вы перечислили, то это будет работать намного хуже. Таким образом, часть вопроса зависит, в частности, от относительных размеров C
и S
, о которых я могу только догадываться. Если E
равно 1000, а C
и S
- по 2-3, то вы будете сканировать 8-21000 строк. Если E
равно 1 миллиону, C
равно 2-3, а S
равно 10-15, вы будете сканировать 26-57 миллионов строк. Если E
равен 1 миллиону, а C
или S
- около 1000, то вы будете сканировать более 1 триллиона строк. Так что нет, это не будет хорошо масштабироваться.
1) ЛЕВЫЕ СОЕДИНЕНИЯ внутри ваших подзапросов игнорируются, поскольку предложения WERE в тех же самых запросах заставляют их быть обычными СОЕДИНЕНИЯМИ. Это не сильно влияет на производительность, но затуманивает ваши намерения.
2) RLIKE (и его псевдоним REGEXP) никогда не используют индексы AFAIK, поэтому они никогда не будут масштабироваться. Я могу только догадываться без выборочных данных, но я бы сказал, что если ваши поиски требуют совпадения границ слов, вам нужно нормализовать свои данные. Даже если ваши названия кажутся естественными строками для хранения, поиск по их части означает, что вы действительно рассматриваете их как набор слов. Поэтому вы должны либо использовать возможности полнотекстового поиска mysql , либо разбить заголовки на отдельные таблицы, в которых хранится одно слово в строке. Одна строка на слово, очевидно, увеличит вашу память, но сделает ваши запросы почти тривиальными, поскольку вы, похоже, выполняете только совпадения целых слов (в отличие от похожих слов, корней слов и т. Д.).
3) Последние левые соединения, которые у вас есть, являются причиной того, что в E x C
части моей формулы вы будете выполнять одну и ту же работу C
раз для каждого упражнения. Теперь, по общему признанию, в большинстве планов запросов подзапросы будут кэшироваться для каждой категории, поэтому на практике это не так плохо, как я предполагаю, но это будет не так в каждом случае, поэтому я даю вам сценарий наихудшего случая. Даже если вы сможете убедиться, что у вас есть правильные индексы, а оптимизатор запросов избежал всех этих дополнительных сканирований таблиц, вы все равно будете возвращать много избыточных данных, потому что ваши результаты будут выглядеть примерно так:
Exercise 1 info
Exercise 1 info
Exercise 1 info
Exercise 2 info
Exercise 2 info
Exercise 2 info
etc
потому что каждая строка упражнения дублируется для каждой записи категории упражнения, даже если вы ничего не возвращаете из категории или категории упражнения (а category.ID в вашем первом подзапросе фактически ссылается на категории, объединенные в этом подзапросе, а НЕ из внешнего запрос).
4) Поскольку большинство поисковых систем возвращают результаты с помощью подкачки страниц, я думаю, вам действительно нужны только первые X-результаты. Добавление LIMIT X к вашему запросу или, что еще лучше, LIMIT Y, X, где Y - текущая страница, а X - количество результатов, возвращаемых на страницу, значительно поможет оптимизировать ваш запрос, если ключевые слова для поиска возвращают много результатов.
Если вы можете предоставить нам немного больше информации о ваших данных, я могу обновить свой ответ, чтобы отразить это.
UPDATE
Основываясь на ваших ответах, вот мой предложенный запрос. К сожалению, без полнотекстового поиска или индексированных слов все еще будут проблемы с масштабированием, если ваша таблица категорий или таблица поисковых тегов очень велики.
SELECT exercises.ID AS ID,
exercises.title AS title,
IF(exercises.title RLIKE CONCAT('[[:<:]]',?), 1, 0)
+
(SELECT COUNT(*)
FROM categories
JOIN exerciseCategories ON exerciseCategories.categoryID = categories.ID
WHERE exerciseCategories.exerciseID = exercises.ID
AND categories.title RLIKE CONCAT('[[:<:]]',?))
+
(SELECT COUNT(*)
FROM searchtags
JOIN exerciseSearchtags ON exerciseSearchtags.searchtagID = searchtags.ID
WHERE exerciseSearchtags.exerciseID = exercises.ID
AND searchtags.title RLIKE CONCAT('[[:<:]]',?))
FROM exercises
ЗАКАЗАТЬ ПО Уместности DESCИмея релевантность> 0 LIMIT $ start, $ results
Обычно я бы не рекомендовал предложение HAVING, но оно не будет хуже, чем ваш RLIKE ... ИЛИ RLIKE ... и т. Д.
Это решает мои проблемы № 1, № 3, № 4, но № 2 все еще остается.Учитывая данные вашего примера, я бы предположил, что каждая таблица содержит не более нескольких десятков записей.В этом случае неэффективность RLIKE может быть недостаточно болезненной, чтобы стоить оптимизации одного слова в строке, но вы спрашивали о масштабировании.Только запрос с точным равенством (title = ?
) или запрос с началом (title LIKE 'foo%'
) могут использовать индексы, которые являются абсолютной необходимостью, если вы собираетесь масштабировать строки в любой таблице.RLIKE и REGEXP не соответствуют этим критериям, независимо от того, используется ли регулярное выражение (а у вас запрос типа «содержит», который является худшим случаем).(Важно отметить, что title LIKE CONCAT(?, '%')
НЕ достаточно хорош, потому что mysql видит, что он должен что-то вычислить, и игнорирует его индекс. Вам нужно добавить «%» в вашем приложении.)