Как улучшить производительность запросов с помощью множества JOINs
У меня есть запрос (с целью создания представления), который использует несколько объединений для получения каждого столбца. Производительность быстро ухудшается (экспоненциально?) Для каждого набора добавленных объединений.
Каким будет хороший способ ускорить этот запрос? Пожалуйста, просмотрите комментарии в запросе.
Если это помогает, это использует схему WordPress DB.
Вот скриншот EXPLAIN
![enter image description here]()
ТАБЛИЦА ПРОДУКТОВ
+--+----+
|id|name|
+--+----+
|1 |test|
+--+----+
ТАБЛИЦА METADATA
+----------+--------+-----+
|product_id|meta_key|value|
+----------+--------+-----+
|1 |price |9.99 |
+----------+--------+-----+
|1 |sku |ABC |
+----------+--------+-----+
ТАБЛИЦА TERM_RELATIONSHIPS
+---------+----------------+
|object_id|term_taxonomy_id|
+---------+----------------+
|1 |1 |
+---------+----------------+
|1 |2 |
+---------+----------------+
TERM_TAXONOMY TABLE
+----------------+-------+--------+
|term_taxonomy_id|term_id|taxonomy|
+----------------+-------+--------+
|1 |1 |size |
+----------------+-------+--------+
|2 |2 |stock |
+----------------+-------+--------+
ТАБЛИЦА ТЕРМИНОВ
+-------+-----+
|term_id|name |
+-------+-----+
|1 |500mg|
+-------+-----+
|2 |10 |
+-------+-----+
QUERY
SELECT
products.id,
products.name,
price.value AS price,
sku.value AS sku,
size.name AS size
FROM products
/* These joins are performing quickly */
INNER JOIN `metadata` AS price ON products.id = price.product_id AND price.meta_key = 'price'
INNER JOIN `metadata` AS sku ON products.id = sku.product_id AND sku.meta_key = 'sku'
/* Here the part that is really slowing it down - I run this chunk about 5 times with different strings to match */
INNER JOIN `term_relationships` AS tr ON products.id = tr.object_id
INNER JOIN `term_taxonomy` AS tt
ON tr.term_taxonomy_id = tt.term_taxonomy_id AND tt.taxonomy = 'size'
INNER JOIN `terms` AS size
ON tt.term_id = size.term_id
Ответы
Ответ 1
Вероятность вашей производительности, скорее всего, вызвана соединением с таблицей "term_taxonomy".
Все остальные объединения, похоже, используют первичный ключ (где у вас есть вероятные рабочие индексы). Итак, мое предложение состоит в том, чтобы добавить составной индекс в term_taxonomy_id и term_id (или если вы должны: таксономия). Вот так:
CREATE UNIQUE INDEX idx_term_taxonomy_id_taxonomy
ON term_taxonomy( term_taxonomy_id, taxonomy);
Надеюсь, это поможет вам.
Ответ 2
Убедитесь, что все столбцы, на которых есть "ON" условные операторы, должны быть проиндексированы.
Это значительно улучшит скорость.
Ответ 3
Попробуйте следующее:
SELECT p.id, p.name, MAX(CASE m.meta_key WHEN 'price' THEN m.value ELSE '' END) AS price,
MAX(CASE m.meta_key WHEN 'sku' THEN m.value ELSE '' END) AS sku, s.name AS size
FROM products p
INNER JOIN `metadata` AS m ON p.id = m.product_id
INNER JOIN `term_relationships` AS tr ON p.id = tr.object_id
INNER JOIN `term_taxonomy` AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id AND tt.taxonomy = 'size'
INNER JOIN `terms` AS s ON tt.term_id = s.term_id
GROUP BY p.id;
Если вы все еще обнаруживаете, что ваш запрос медленный, добавьте план EXPLAIN
моего запроса, чтобы я мог найти, какие столбцы нужны INDEX
.
Ответ 4
Declare @query as NVARCHAR(MAX)
set @query = ('SELECT
products.id,
products.name,
price.value AS price,
sku.value AS sku,
size.name AS size
FROM products
INNER JOIN metadata AS price ON products.id = price.product_id AND price.meta_key = price
INNER JOIN metadata AS sku ON products.id = sku.product_id AND sku.meta_key = sku
INNER JOIN term_relationships AS tr ON products.id = tr.object_id
INNER JOIN term_taxonomy AS tt
ON tr.term_taxonomy_id = tt.term_taxonomy_id AND tt.taxonomy = size
INNER JOIN terms AS size
ON tt.term_id = size.term_id
into #t')
exec(@query);
select * from #t
Я надеюсь, что вышеупомянутый способ уменьшит использование времени или создаст временную таблицу со всеми выбранными вами полями и обновит временную таблицу, присоединившись к временной таблице ко всем другим таблицам, также может быть эффективным, ну, я не уверенный об этом, но даже я жду вашего результата, поскольку ваш вопрос кажется интересным.
Ответ 5
Ниже script отформатирован в соответствии с правилами SQL Server. Вы можете изменить это в соответствии с правилами MySQL и попробовать его -
SELECT
P.id,
P.name,
PIVOT_METADATA.price,
PIVOT_METADATA.sku,
size.name AS size
FROM products P (NOLOCK)
INNER JOIN term_relationships AS tr (NOLOCK)
ON P.id = tr.object_id
INNER JOIN term_taxonomy AS tt (NOLOCK)
ON tr.term_taxonomy_id = tt.term_taxonomy_id AND tt.taxonomy = 'size'
INNER JOIN terms AS size (NOLOCK)
ON tt.term_id = size.term_id
INNER JOIN METADATA (NOLOCK)
PIVOT
(
MAX(value)
FOR [meta_key] IN (price,sku)
)AS PIVOT_METADATA
ON P.id = PIVOT_METADATA.product_id
То, что я чувствую, может быть узким местом в вашем запросе. Вы присоединяетесь к метаданным 2 раза. Поскольку в ваших таблицах есть отношения "один-ко-многим", метаданные 2-join не пострадают, но после этого, когда вы присоединяетесь к большему количеству таблиц, количество строк из-за увеличения отношения "один ко многим" - и, следовательно, падение производительности.
Что я пытался достичь. Я убеждаюсь, что как можно больше взаимосвязей 1 к 1. Чтобы сделать это, я сделал Pivot по метаданным и цене и ску в качестве столбцов. Теперь мой идентификатор продукта должен иметь только одну строку в метаданных. alos, я убедился, что присоединяюсь к этому пикоту в самом конце.
Попробуй. Пожалуйста, поделитесь ожидаемой производительностью, количеством записей, которые у вас есть, а также тем, что вы получаете с моим asnwer.
Ответ 6
METADATA_TABLE и TERM_RELATIONSHIP_TABLE не имеют прокси-ключа. Когда в этих таблицах будут огромные записи, ваша производительность запроса будет удалена.
Контрольные точки для повышения производительности.
- Все таблицы должны иметь первичный ключ. Это связано с тем, что строки в таблице будут физически отсортированы.
- Для маленьких и запросов, содержащих несколько таблиц, содержащих первичный ключ в таблице, будет достаточно.
Если вы по-прежнему хотите повысить производительность, создайте некластеризованный индекс для столбцов, например поле * object_Id таблицы term_relationships *. Некластеризованный индекс должен быть создан для тех столбцов в таблице, которые участвуют в операции объединения.
Однако следует отметить, что некластеризованный индекс должен быть очень меньше в тех таблицах, где происходит несколько вложений и обновлений.
Это не простой вопрос, и на него нельзя ответить только в зависимости от времени выполнения. Существуют и другие факторы, которые влияют на ответ, особенно если среда, в которой выполняется хранимая процедура, в значительной степени является транзакционной.
Здесь вы можете найти
Ответ 7
Я бы предложил те:
- Рассмотрите возможность сокращения этих объединений с уровня бизнеса;
- Если это невозможно сделать из "верхнего" (бизнес-уровня), а данные не для реального времени, я бы предложил подготовить таблицу памяти (я знаю, что решение не идеально). И непосредственно выберите данные из таблицы памяти.
По моему опыту:
- "joins" - это убийца для производительности, чем больше ваши данные, тем больше боли вы почувствуете;
- Попытайтесь избавиться от объединений, а не пытайтесь повысить производительность запросов, сохраняя соединения, если только вам это не нужно. Обычно я попытаюсь исправить эти проблемы с "сверху" до "снизу"
- Последнее предложение - если все выше не работает. Я буду рассматривать "map/reduce + fulltext search", если это стоит того сделать.
(Простите меня, я не предлагал решения для улучшения производительности вашего запроса.)