TL; DR
Como esta pregunta sigue obteniendo puntos de vista, la resumiré aquí para que los recién llegados no tengan que sufrir la historia:
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
Me doy cuenta de que esto podría no ser un problema de todos, pero al resaltar la sensibilidad de las cláusulas ON, podría ayudarlo a mirar en la dirección correcta. En cualquier caso, el texto original está aquí para futuros antropólogos:
Texto original
Considere la siguiente consulta simple (solo 3 tablas involucradas)
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
Esta es una consulta bastante simple, la única parte confusa es la última unión de categoría, es así porque el nivel de categoría 5 puede o no existir. Al final de la consulta, estoy buscando información de categoría por ID de producto (ID de SKU), y ahí es donde entra la tabla muy grande category_link. Finalmente, la tabla #Ids es solo una tabla temporal que contiene 10'000 Ids.
Cuando se ejecuta, obtengo el siguiente plan de ejecución real:
Como puede ver, casi el 90% del tiempo se gasta en los bucles anidados (unión interna). Aquí hay información adicional sobre esos bucles anidados:
Tenga en cuenta que los nombres de la tabla no coinciden exactamente porque edité los nombres de la tabla de consulta para facilitar su lectura, pero es bastante fácil de combinar (ads_alt_category = category). ¿Hay alguna forma de optimizar esta consulta? También tenga en cuenta que en producción, la tabla temporal #Ids no existe, es un parámetro de valor de tabla de los mismos 10'000 Ids pasados al procedimiento almacenado.
Información adicional:
- índices de categoría en category_id y parent_category_id
- category_voc index en category_id, language_code
- category_link index en sku_id, category_id
Editar (resuelto)
Como se señaló en la respuesta aceptada, el problema era la cláusula OR en la categoría JOIN. Sin embargo, el código sugerido en la respuesta aceptada es muy lento, incluso más lento que el código original. Una solución mucho más rápida y también más limpia es simplemente reemplazar la condición JOIN actual con lo siguiente:
JOIN category_link l on l.sku_id IN (SELECT value FROM @p1) AND l.category_id = COALESCE(c5.category_id, c4.category_id)
Este ajuste minucioso es la solución más rápida, probado contra la doble unión de la respuesta aceptada y también probado contra la APLICACIÓN CRUZADA según lo sugerido por valverij.
fuente
Respuestas:
El problema parece estar en esta parte del código:
or
en condiciones de unión siempre es sospechoso. Una sugerencia es dividir esto en dos combinaciones:Luego tiene que modificar el resto de la consulta para manejar esto. . .
coalesce(l1.sku_id, l2.sku_id)
por ejemplo en laselect
cláusulafuente
JOIN
de unaCROSS APPLY
con laIN
conmutación a unaEXISTS
en elAPPLY
'sWHERE
cláusula.ON l.category_id = ISNULL(c5.category_id, c4.category_id
hizo el truco.coalesce()
empuja al optimizador en la dirección correcta.Como otro usuario mencionó, esta unión es probablemente la causa:
Además de dividirlos en varias combinaciones, también puede probar un
CROSS APPLY
Desde el enlace de MSDN arriba:
Básicamente,
APPLY
es como una subconsulta que filtra los registros de la derecha primero y luego aplica al resto de su consulta.Este artículo hace un muy buen trabajo al explicar qué es y cuándo usarlo: http://explainextended.com/2009/07/16/inner-join-vs-cross-apply/
Sin embargo, es importante tener en cuenta que
CROSS APPLY
no siempre funciona más rápido que unINNER JOIN
. En muchas situaciones, probablemente será lo mismo. Sin embargo, en casos raros, lo he visto más lento (de nuevo, todo esto depende de la estructura de su tabla y de la consulta en sí).Como regla general, si me encuentro uniéndome a una tabla con demasiadas declaraciones condicionales, entonces tiendo a inclinarme hacia
APPLY
También una nota divertida:
OUTER APPLY
actuará como unLEFT JOIN
Además, tome nota de mi elección para usar en
EXISTS
lugar deIN
. Al realizarIN
una subconsulta, recuerde que devolverá todo el conjunto de resultados, incluso después de haber encontrado su valor. ConEXISTS
, sin embargo, detendrá la subconsulta en el instante en que encuentre una coincidencia.fuente
AND x.cat = c4.cat OR x.cat = c5.cat
porx.cat = ISNULL(c5.cat, c4.cat)
y deshacerse de la cláusula IN hizo esta la solución de segundo más rápido, y digno de un upvote, porque es bastante informativo.