Мне удалось собрать запрос, который работает для моих нужд, хотя и более сложный, чем я надеялся. Но для размера таблиц запрос медленнее, чем должен быть (0,17 с). Причина, основанная на приведенном ниже EXPLAIN
, заключается в том, что в таблице meta_relationships
есть сканирование таблицы из-за того, что она имеет COUNT
в предложении WHERE
на движке innodb
.
Query:
SELECT
posts.post_id,posts.post_name,
GROUP_CONCAT(IF(meta_data.type = 'category', meta.meta_name,null)) AS category,
GROUP_CONCAT(IF(meta_data.type = 'tag', meta.meta_name,null)) AS tag
FROM posts
RIGHT JOIN meta_relationships ON (posts.post_id = meta_relationships.object_id)
LEFT JOIN meta_data ON meta_relationships.meta_data_id = meta_data.meta_data_id
LEFT JOIN meta ON meta_data.meta_id = meta.meta_id
WHERE meta.meta_name = computers AND meta_relationships.object_id
NOT IN (SELECT meta_relationships.object_id FROM meta_relationships
GROUP BY meta_relationships.object_id HAVING count(*) > 1)
GROUP BY meta_relationships.object_id
В этом конкретном запросе выбираются сообщения, которые имеют ТОЛЬКО категорию computers
. Цель count > 1
заключается в исключении сообщений, содержащих computers/hardware
, computers/software
и т.д. Чем больше выбранных категорий, тем выше будет счет.
В идеале я бы хотел, чтобы он функционировал следующим образом:
WHERE meta.meta_name IN ('computers') AND meta_relationships.meta_order IN (0)
или
WHERE meta.meta_name IN ('computers','software')
AND meta_relationships.meta_order IN (0,1)
и т.д..
Но, к сожалению, это не работает, потому что оно не учитывает, что может быть meta_relationships.meta_order
= 2.
Я пробовал...
WHERE meta.meta_name IN ('computers')
GROUP BY meta_relationships.meta_order
HAVING meta_relationships.meta_order IN (0) AND meta_relationships.meta_order NOT IN (1)
но он не возвращает правильное количество строк.
EXPLAIN:
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY meta ref PRIMARY,idx_meta_name idx_meta_name 602 const 1 Using where; Using index; Using temporary; Using filesort
1 PRIMARY meta_data ref PRIMARY,idx_meta_id idx_meta_id 8 database.meta.meta_id 1
1 PRIMARY meta_relationships ref idx_meta_data_id idx_meta_data_id 8 database.meta_data.meta_data_id 11 Using where
1 PRIMARY posts eq_ref PRIMARY PRIMARY 4 database.meta_relationships.object_id 1
2 MATERIALIZED meta_relationships index NULL idx_object_id 4 NULL 14679 Using index
Таблицы/Индексы:
мета
Эта таблица содержит имена категорий и тегов.
индексы:
ПЕРВИЧНЫЙ КЛЮЧ (meta_id
), КЛЮЧ idx_meta_name
(meta_name
)
meta_dataSTRONG >
В этой таблице содержатся дополнительные данные о категориях и тегах, таких как тип (категория или тег), описание, родительский элемент, счет.
индексы:
ПЕРВИЧНЫЙ КЛЮЧ (meta_data_id
), КЛЮЧ idx_meta_id
(meta_id
)
meta_relationships
Это таблица соединений/поиска. Он содержит внешний ключ для posts_id, внешний ключ для meta_data_id, а также содержит порядок категорий.
индексы:
ПЕРВИЧНЫЙ КЛЮЧ (relationship_id
), КЛЮЧ idx_object_id
(object_id
), КЛЮЧ idx_meta_data_id
(meta_data_id
)
- Счет позволяет мне выбирать только сообщения с правильным уровнем категории. Например, компьютеры категории имеют сообщения только с категорией компьютеров, но также имеют сообщения с компьютерами/оборудованием. Счетчик фильтрует сообщения, содержащие эти дополнительные категории. Надеюсь, это имеет смысл.
- Я считаю, что ключ к оптимизации запроса состоит в том, чтобы полностью уйти от выполнения
COUNT
. - Альтернативой
COUNT
может быть вместо этого использоватьmeta_relationships.meta_order
илиmeta_data.parent
. - Таблица
meta_relationships
будет расти быстро и с текущим размером (~ 15K строк). Я надеюсь получить время выполнения за 100 секунд, а не за 10 секунд. - Поскольку в предложении
WHERE
для каждой категории/тега должно быть несколько условий, любой предпочтительный для динамического запроса ответ считается предпочтительным. - Я создал IDE с пример данных.
Как я могу оптимизировать этот запрос?
EDIT:
Я никогда не мог найти оптимальное решение этой проблемы. Это была комбинация рекомендаций smcjones по улучшению индексов, для которых я бы рекомендовал сделать EXPLAIN
и посмотреть EXPLAIN Output Format, затем изменить индексы к тому, что дает вам лучшую производительность.
Кроме того, рекомендация hpf добавить еще один столбец с общим счетом очень помогла. В конце концов, после изменения индексов, я перешел к этому запросу.
SELECT posts.post_id,posts.post_name,
GROUP_CONCAT(IF(meta_data.type = 'category', meta.meta_name,null)) AS category,
GROUP_CONCAT(IF(meta_data.type = 'tag', meta.meta_name,null)) AS tag
FROM posts
JOIN meta_relationships ON meta_relationships.object_id = posts.post_id
JOIN meta_data ON meta_relationships.meta_data_id = meta_data.meta_data_id
JOIN meta ON meta_data.meta_id = meta.meta_id
WHERE posts.meta_count = 2
GROUP BY posts.post_id
HAVING category = 'category,subcategory'
После избавления от COUNT
, большой производительный убийца был GROUP BY
и ORDER BY
, но индексы - ваш лучший друг. Я узнал, что при выполнении GROUP BY
предложение WHERE
очень важно, тем более конкретным вы можете добиться лучшего.