¿Hay alguna diferencia de rendimiento entre CTE, subconsulta, tabla temporal o variable de tabla?

222

En esta excelente pregunta SO , se discutieron las diferencias entre CTEy sub-queries.

Me gustaría preguntar específicamente:

¿En qué circunstancia cada uno de los siguientes es más eficiente / más rápido?

  • CTE
  • Subconsulta
  • Tabla temporal
  • Variable de tabla

Tradicionalmente, he usado mucho temp tablesen el desarrollo stored procedures, ya que parecen más legibles que muchas subconsultas entrelazadas.

Non-recursive CTEs encapsulan conjuntos de datos muy bien y son muy legibles, pero ¿hay circunstancias específicas en las que se pueda decir que siempre funcionarán mejor? ¿O se trata de tener que jugar con las diferentes opciones para encontrar la solución más eficiente?


EDITAR

Recientemente me han dicho que, en términos de eficiencia, las tablas temporales son una buena primera opción, ya que tienen un histograma asociado, es decir, estadísticas.

whytheq
fuente
44
Respuesta general: depende. Y depende de muchos factores, cualquier declaración general es probablemente falsa, en algunas situaciones. Básicamente: necesita probar y medir, ¡vea cuál funciona mejor para usted!
marc_s
@marc_s - ok; ¿Quizás esta pregunta debería cerrarse por ser subjetiva? Tenga en cuenta que muchas preguntas SQL sobre SO podrían considerarse subjetivas.
whytheq
1
Podría cerrarse como demasiado amplio, y estoy de acuerdo con usted, muchas cosas y temas en SQL realmente obtendrán una respuesta , depende . A veces, uno puede enumerar dos o tres criterios para tomar una decisión, pero con su pregunta aquí, es casi imposible dar un buen consejo, depende de mucho, las estructuras de su tabla, los datos en esas tablas, las consultas que está utilizando, su estrategia de indexación y mucho, mucho más ...
marc_s
@marc_s sería bueno intentarlo y mantenerlo: ¿algún consejo sobre posibles ediciones en OP para intentar hacerlo más específico y estrecho?
whytheq
Tenga en cuenta que esta pregunta es específica de SQL Server. Para otros DB como postgres, un CTE suele ser mucho más lento que las subconsultas equivalentes (consulte http://blog.2ndquadrant.com/postgresql-ctes-are-optimization-fences/ )
Jay

Respuestas:

243

SQL es un lenguaje declarativo, no un lenguaje de procedimiento. Es decir, construye una declaración SQL para describir los resultados que desea. No le está diciendo al motor SQL cómo hacer el trabajo.

Como regla general, es una buena idea dejar que el motor SQL y el optimizador SQL encuentren el mejor plan de consulta. Hay muchos años-persona de esfuerzo para desarrollar un motor SQL, así que deje que los ingenieros hagan lo que saben hacer.

Por supuesto, hay situaciones en las que el plan de consulta no es óptimo. Luego, desea usar sugerencias de consulta, reestructurar la consulta, actualizar estadísticas, usar tablas temporales, agregar índices, etc. para obtener un mejor rendimiento.

En cuanto a tu pregunta. El rendimiento de los CTE y las subconsultas debería, en teoría, ser el mismo, ya que ambos proporcionan la misma información al optimizador de consultas. Una diferencia es que un CTE usado más de una vez podría identificarse y calcularse fácilmente una vez. Los resultados podrían almacenarse y leerse varias veces. Desafortunadamente, SQL Server no parece aprovechar este método básico de optimización (podría llamarse a esto eliminación de subconsulta común).

Las tablas temporales son una cuestión diferente, ya que proporciona más orientación sobre cómo se debe ejecutar la consulta. Una diferencia importante es que el optimizador puede usar estadísticas de la tabla temporal para establecer su plan de consulta. Esto puede resultar en ganancias de rendimiento. Además, si tiene un CTE (subconsulta) complicado que se usa más de una vez, almacenarlo en una tabla temporal a menudo le dará un impulso al rendimiento. La consulta se ejecuta solo una vez.

La respuesta a su pregunta es que necesita jugar para obtener el rendimiento que espera, particularmente para consultas complejas que se ejecutan regularmente. En un mundo ideal, el optimizador de consultas encontraría la ruta de ejecución perfecta. Aunque a menudo lo hace, es posible que pueda encontrar una manera de obtener un mejor rendimiento.

Gordon Linoff
fuente
11
Algunas investigaciones de Microsoft sobre posibles mejoras futuras en esta área se encuentran en la publicación "Explotación eficiente de subexpresiones similares para el procesamiento de consultas" Disponible desde aquí
Martin Smith
3
Dado que ese documento se presentó en 2007, ¿alguna idea de si lo han incorporado en SQL Server 2012?
Gordon Linoff
3
Una gran respuesta! Solo para enfatizar: SQL es un lenguaje declarativo, y no controlamos CÓMO se extraen los datos. Por lo tanto, el rendimiento / velocidad varía de una consulta a otra.
Simcha Khabinsky
2
@RGS. . . Los índices en tablas temporales definitivamente mejoran las consultas que pueden aprovechar esos índices, como con los índices en una tabla permanente. Pero, si materializa una subconsulta como una tabla temporal, puede perder la ventaja de los índices en las tablas originales.
Gordon Linoff
2
@RGS. . Cuando un motor de base de datos materializa una subconsulta / CTE en el curso de la ejecución de una consulta compleja, no agrega índices en la materialización. Puede hacer esto manualmente usando tablas temporales.
Gordon Linoff
77

No hay regla Encuentro que los CTE son más legibles y los uso a menos que que presenten algún problema de rendimiento, en cuyo caso investigo el problema real en lugar de adivinar que el CTE es el problema e intento volver a escribirlo con un enfoque diferente. Por lo general, el problema tiene más que la forma en que elegí declarar declarativamente mis intenciones con la consulta.

Ciertamente, hay casos en los que puede desentrañar CTE o eliminar subconsultas y reemplazarlas con una tabla #temp y reducir la duración. Esto puede deberse a varias cosas, como las estadísticas obsoletas, la incapacidad de obtener estadísticas precisas (por ejemplo, unirse a una función con valores de tabla), el paralelismo o incluso la incapacidad de generar un plan óptimo debido a la complejidad de la consulta ( en cuyo caso dividirlo puede darle al optimizador una oportunidad de lucha). Pero también hay casos en los que la E / S involucrada en la creación de una tabla #temp puede superar los otros aspectos de rendimiento que pueden hacer que una forma de plan particular usando un CTE sea menos atractiva.

Honestamente, hay demasiadas variables para proporcionar una respuesta "correcta" a su pregunta. No hay una forma predecible de saber cuándo una consulta puede inclinarse a favor de un enfoque u otro; solo sepa que, en teoría, la misma semántica para un CTE o una subconsulta única debería ejecutarse exactamente igual. Creo que su pregunta sería más valiosa si presenta algunos casos en los que esto no es cierto: puede ser que haya descubierto una limitación en el optimizador (o haya descubierto una conocida), o que sus consultas no sean semánticamente equivalentes o ese contiene un elemento que frustra la optimización.

Por lo tanto, sugeriría escribir la consulta de la manera que le parezca más natural, y solo se desviará cuando descubra un problema de rendimiento real que tiene el optimizador. Personalmente los clasifico CTE, luego subconsulta, con la tabla #temp como último recurso.

Aaron Bertrand
fuente
44
+1 resultó ser una pregunta bastante subjetiva; Espero que no se cierre por ser demasiado vago, ya que las respuestas hasta ahora son informativas. Me doy cuenta :-) que no te gusta cuando cambian las preguntas, pero ¿tienes alguna sugerencia para acotar la pregunta en el OP?
whytheq
2
Creo que esta pregunta está bien, notarás que todavía no hay un solo voto para cerrar, pero si las respuestas comienzan a fallar violentamente, probablemente se cerrará. Como sugerí en mi respuesta, si tiene un caso particular en el que ve una gran diferencia entre un CTE y una subconsulta, comience una nueva pregunta con las consultas reales y los planes de ejecución (y podría encajar mejor en dba.se ) . Solo tenga en cuenta que la respuesta para ayudar con esa consulta podría no ser la misma respuesta para una consulta diferente con el mismo escenario.
Aaron Bertrand
Justo debajo de su pregunta hay enlaces link / edit / close / flag: si ha habido votos para cerrar la pregunta, verá close (n)dónde nrepresenta el número de usuarios que votaron para cerrar su pregunta. Si hace clic en el enlace, verá los motivos que seleccionaron esos usuarios.
Aaron Bertrand
@whytheq también vea esta reciente publicación de blog de Bob Beauchemin . No trata el CTE versus la subconsulta específicamente, pero se aplica el mismo tipo de concepto: si elige un patrón no intuitivo por razones de rendimiento, documente la basura y vuelva a visitarlo para asegurarse de que la peculiaridad que descubrió aún sea real. Incluso podría sugerir dejar comentada la versión más natural de la consulta, a menos que tenga un sistema de control de fuente confiable que contenga la versión anterior.
Aaron Bertrand
1
Enlace fijo arriba: sqlskills.com/blogs/bobb/…
ADJenks
19

#temp está materalizado y CTE no.

CTE es solo una sintaxis, por lo que en teoría es solo una subconsulta. Se ejecuta. #temp se materializa. Por lo tanto, un CTE costoso en una unión que se ejecuta muchas veces puede ser mejor en un #temp. Por otro lado, si se trata de una evaluación fácil que no se ejecuta, pero algunas veces no vale la pena la sobrecarga de #temp.

Hay algunas personas en SO a las que no les gusta la variable de tabla, pero me gustan porque se materializan y son más rápidas de crear que #temp. Hay momentos en que el optimizador de consultas funciona mejor con un #temp en comparación con una variable de tabla.

La capacidad de crear un PK en una variable #temp o table le da al optimizador de consultas más información que un CTE (ya que no puede declarar un PK en un CTE).

paparazzo
fuente
¿Cuál es el acrónimo "TVP" ... algo similar a #temp?
whytheq
TVP se está convirtiendo en un término común, porque suena impresionante (para algunos). En resumen, un TVP es una tabla que se pasa como parámetro. Cualquiera que haya usado variables de tabla estará en casa con ellas.
WonderWorker
1
ADVERTENCIA: ¡los TVP no tienen planes de ejecución! No use TVP para otra cosa que no sea la más simple de las listas de búsqueda cortas. Si realiza uniones complejas, inserciones o actualizaciones en ellos, podría encontrarse con problemas de optimización masiva. Confía en mí, me quemé por esto.
Heliac
12

Solo 2 cosas que creo hacen SIEMPRE preferible usar una tabla # Temp en lugar de un CTE son:

  1. No puede poner una clave primaria en un CTE, por lo que los datos a los que accede el CTE tendrán que atravesar cada uno de los índices en las tablas del CTE en lugar de simplemente acceder al PK o al Índice en la tabla temporal.

  2. Debido a que no puede agregar restricciones, índices y claves primarias a un CTE, son más propensos a la aparición de errores y datos incorrectos.


-un día cuando ayer

Aquí hay un ejemplo en el que las restricciones #table pueden evitar datos incorrectos, que no es el caso en los CTE

DECLARE @BadData TABLE ( 
                       ThisID int
                     , ThatID int );
INSERT INTO @BadData
       ( ThisID
       , ThatID
       ) 
VALUES
       ( 1, 1 ),
       ( 1, 2 ),
       ( 2, 2 ),
       ( 1, 1 );

IF OBJECT_ID('tempdb..#This') IS NOT NULL
    DROP TABLE #This;
CREATE TABLE #This ( 
             ThisID int NOT NULL
           , ThatID int NOT NULL
                        UNIQUE(ThisID, ThatID) );
INSERT INTO #This
SELECT * FROM @BadData;
WITH This_CTE
     AS (SELECT *
           FROM @BadData)
     SELECT *
       FROM This_CTE;
ShanksPranks
fuente
3
ALWAYSestá un poco lejos pero gracias por la respuesta. En términos de legibilidad, el uso de CTE puede ser algo bueno.
whytheq
3
No entiendo tu segundo punto en absoluto. A mi modo de ver, la consulta que define el CTE es análoga a las restricciones que pondría en la tabla temporal, y señala que la primera puede comprender predicados complejos arbitrariamente, mientras que la segunda es mucho más limitada (por ejemplo, la CHECKrestricción que se refiere a múltiples filas / tablas es No permitido). ¿Puede publicar un ejemplo donde un CTE exhiba un error que el equivalente de la tabla temporal no?
cuando el