TL; DR
Поскольку этот вопрос продолжает получать взгляды, я кратко изложу его здесь, чтобы новичкам не пришлось страдать от истории:
JOIN table t ON t.member = @value1 OR t.member = @value2 -- this is slow as hell
JOIN table t ON t.member = COALESCE(@value1, @value2) -- this is blazing fast
-- Note that here if @value1 has a value, @value2 is NULL, and vice versa
Я понимаю, что это может быть проблемой не всех, но, подчеркнув чувствительность предложений ON, это может помочь вам смотреть в правильном направлении. В любом случае оригинальный текст здесь для будущих антропологов:
Оригинальный текст
Рассмотрим следующий простой запрос (задействованы только 3 таблицы)
SELECT
l.sku_id AS ProductId,
l.is_primary AS IsPrimary,
v1.category_name AS Category1,
v2.category_name AS Category2,
v3.category_name AS Category3,
v4.category_name AS Category4,
v5.category_name AS Category5
FROM category c4
JOIN category_voc v4 ON v4.category_id = c4.category_id and v4.language_code = 'en'
JOIN category c3 ON c3.category_id = c4.parent_category_id
JOIN category_voc v3 ON v3.category_id = c3.category_id and v3.language_code = 'en'
JOIN category c2 ON c2.category_id = c3.category_id
JOIN category_voc v2 ON v2.category_id = c2.category_id and v2.language_code = 'en'
JOIN category c1 ON c1.category_id = c2.parent_category_id
JOIN category_voc v1 ON v1.category_id = c1.category_id and v1.language_code = 'en'
LEFT OUTER JOIN category c5 ON c5.parent_category_id = c4.category_id
LEFT OUTER JOIN category_voc v5 ON v5.category_id = c5.category_id and v5.language_code = @lang
JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
(
l.category_id = c4.category_id OR
l.category_id = c5.category_id
)
WHERE c4.[level] = 4 AND c4.version_id = 5
Это довольно простой запрос, единственная запутанная часть - последнее объединение категории, именно так, потому что категория 5 уровня может существовать или не существовать. В конце запроса я ищу информацию о категории для каждого идентификатора продукта (SKU ID), и именно здесь появляется очень большая таблица category_link. Наконец, таблица #Ids - это просто временная таблица, содержащая 10 000 идентификаторов.
При выполнении я получаю следующий фактический план выполнения:
Как вы можете видеть, почти 90% времени проводится во вложенных циклах (Inner Join). Вот дополнительная информация об этих вложенных циклах:
Обратите внимание, что имена таблиц не совпадают точно, потому что я отредактировал имена таблиц запросов для удобства чтения, но их довольно легко сопоставить (ads_alt_category = category). Есть ли способ оптимизировать этот запрос? Также обратите внимание, что в производственной среде временная таблица #Ids не существует, это параметр с табличным значением тех же 10 000 идентификаторов, которые передаются в хранимую процедуру.
Дополнительная информация:
- индексы категорий по category_id и parent_category_id
- указатель категории по номеру для идентификатора категории, код_языка
- указатель category_link по sku_id, category_id
Редактировать (решено)
Как указано в принятом ответе, проблема заключалась в предложении OR в соединении category_link JOIN. Однако код, предложенный в принятом ответе, очень медленный, даже медленнее, чем исходный код. Более быстрое, а также более чистое решение - просто заменить текущее условие JOIN следующим:
JOIN category_link l on l.sku_id IN (SELECT value FROM @p1) AND l.category_id = COALESCE(c5.category_id, c4.category_id)
Эта минутная настройка является самым быстрым решением, проверенным на соответствие двойному соединению из принятого ответа, а также проверенным на CROSS APPLY, как предложено valverij.
источник
Ответы:
Проблема, кажется, находится в этой части кода:
or
в условиях соединения всегда подозрительно. Одно предложение состоит в том, чтобы разделить это на два соединения:Затем вам нужно изменить остальную часть запроса, чтобы справиться с этим. , ,
coalesce(l1.sku_id, l2.sku_id)
например вselect
пункте.источник
JOIN
кCROSS APPLY
сIN
переключаясь наEXISTS
вAPPLY
«sWHERE
п.ON l.category_id = ISNULL(c5.category_id, c4.category_id
сделал свое дело.coalesce()
толкает оптимизатор в правильном направлении.Как упомянул другой пользователь, это соединение, вероятно, является причиной:
Помимо разделения их на несколько соединений, вы также можете попробовать
CROSS APPLY
Из ссылки MSDN выше:
По сути,
APPLY
это как подзапрос, который сначала отфильтровывает записи справа, а затем применяет их к остальной части вашего запроса.Эта статья очень хорошо объясняет, что это такое и когда его использовать: http://explainextended.com/2009/07/16/inner-join-vs-cross-apply/
Важно отметить, однако, что
CROSS APPLY
не всегда работает быстрее, чемINNER JOIN
. Во многих ситуациях это, вероятно, будет примерно таким же. В редких случаях, тем не менее, я на самом деле видел это медленнее (опять же, все это зависит от структуры вашей таблицы и самого запроса).Как правило, если я присоединяюсь к столу со слишком большим количеством условных выражений, то склоняюсь к
APPLY
Также забавная заметка:
OUTER APPLY
будет действовать какLEFT JOIN
Кроме того, пожалуйста, обратите внимание на мой выбор, чтобы использовать,
EXISTS
а неIN
. При выполненииIN
подзапроса помните, что он вернет весь набор результатов, даже после того, как найдет ваше значение. ОднакоEXISTS
при этом он остановит подзапрос, как только найдет совпадение.источник
AND x.cat = c4.cat OR x.cat = c5.cat
поx.cat = ISNULL(c5.cat, c4.cat)
и избавлении от пункта IN сделал это второе самое быстрое решение, и достойным upvote, потому что это довольно информативны.