¿Por qué la función LEN () subestima mal la cardinalidad en SQL Server 2014?


26

Tengo una tabla con una columna de cadena y un predicado que busca filas con una cierta longitud. En SQL Server 2014, veo una estimación de 1 fila independientemente de la longitud que estoy buscando. Esto está generando planes muy pobres porque en realidad hay miles o incluso millones de filas y SQL Server está eligiendo colocar esta tabla en el lado externo de un bucle anidado.

¿Existe una explicación para la estimación de cardinalidad de 1,0003 para SQL Server 2014 mientras que SQL Server 2012 estima 31,622 filas? ¿Hay una buena solución?

Aquí hay una breve reproducción del problema:

-- Create a table with 1MM rows of dummy data
CREATE TABLE #customers (cust_nbr VARCHAR(10) NOT NULL)
GO

INSERT INTO #customers WITH (TABLOCK) (cust_nbr)
    SELECT TOP 1000000 
        CONVERT(VARCHAR(10),
        ROW_NUMBER() OVER (ORDER BY (SELECT NULL))) AS cust_nbr
    FROM master..spt_values v1
    CROSS JOIN master..spt_values v2
GO

-- Looking for string of a certain length.
-- While both CEs yield fairly poor estimates, the 2012 CE is much
-- more conservative (higher estimate) and therefore much more likely
-- to yield an okay plan rather than a drastically understimated loop join.
-- 2012: 31,622 rows estimated, 900K rows actual
-- 2014: 1 row estimated, 900K rows actual
SELECT COUNT(*)
FROM #customers
WHERE LEN(cust_nbr) = 6
OPTION (QUERYTRACEON 9481) -- Optionally, use 2012 CE
GO

Aquí hay un script más completo que muestra pruebas adicionales

También leí el documento sobre el Estimador de cardinalidad de SQL Server 2014 , pero no encontré nada que aclarara la situación.

Respuestas:


20

Para el CE heredado, veo que la estimación es para 3.16228% de las filas, y esa es una heurística de "número mágico" utilizada para predicados de columna = literales (hay otras heurísticas basadas en la construcción de predicados, pero se LENenvuelve alrededor de la columna para los resultados de CE heredados coinciden con este marco de conjeturas). Puede ver ejemplos de esto en una publicación sobre Adivinanzas de selectividad en ausencia de estadísticas de Joe Sack, y Estimación de comparación constante-constante de Ian Jose.

-- Legacy CE: 31622.8 rows
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  ( QUERYTRACEON 9481); -- Legacy CE
GO

Ahora, en cuanto al nuevo comportamiento CE, parece que ahora es visible para el optimizador (lo que significa que podemos usar estadísticas). Realicé el ejercicio de mirar la salida de la calculadora a continuación, y puedes ver la generación automática de estadísticas asociada como un puntero:

-- New CE: 1.00007 rows
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  ( QUERYTRACEON 2312 ); -- New CE
GO

-- View New CE behavior with 2363 (for supported option use XEvents)
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  (QUERYTRACEON 2312, QUERYTRACEON 2363, QUERYTRACEON 3604, RECOMPILE); -- New CE
GO

/*
Loaded histogram for column QCOL:
[tempdb].[dbo].[#customers].cust_nbr from stats with id 2
Using ambient cardinality 1e+006 to combine distinct counts:
  999927

Combined distinct count: 999927
Selectivity: 1.00007e-006
Stats collection generated:
  CStCollFilter(ID=2, CARD=1.00007)
      CStCollBaseTable(ID=1, CARD=1e+006 TBL: #customers)

End selectivity computation
*/

EXEC tempdb..sp_helpstats '#customers';


--Check out AVG_RANGE_ROWS values (for example - plenty of ~ 1)
DBCC SHOW_STATISTICS('tempdb..#customers', '_WA_Sys_00000001_B0368087');
--That's my Stats name yours is subject to change

Desafortunadamente, la lógica se basa en una estimación del número de valores distintos, que no se ajusta por el efecto de la LENfunción.

Posible solución

Puede obtener una estimación basada en trie en ambos modelos CE reescribiendo el LENcomo LIKE:

SELECT COUNT_BIG(*)
FROM #customers AS C
WHERE C.cust_nbr LIKE REPLICATE('_', 6);

Como el plan


Información sobre las banderas de seguimiento utilizadas:

  • 2363: muestra mucha información, incluidas las estadísticas que se están cargando.
  • 3604: imprime la salida de los comandos DBCC en la pestaña de mensajes.

13

¿Existe una explicación para la estimación de cardinalidad de 1,0003 para SQL 2014 mientras que SQL 2012 estima 31,622 filas?

Creo que la respuesta de @ Zane cubre esta parte bastante bien.

¿Hay una buena solución?

Puede intentar crear una columna calculada no persistente para LEN(cust_nbr)y (opcionalmente) crear un índice no agrupado en esa columna calculada. Eso debería darte estadísticas precisas.

Hice algunas pruebas y esto es lo que encontré:

  • Las estadísticas se crearon automáticamente en la columna calculada no persistente, cuando no se definió ningún índice en ella.
  • Agregar el índice no agrupado en la columna calculada no solo no ayudó, sino que también perjudicó un poco el rendimiento. CPU y tiempos transcurridos ligeramente más altos. Costo estimado ligeramente más alto (lo que sea que valga).
  • Hacer la columna calculada como PERSISTED(sin índice) fue mejor que las otras dos variaciones. Las filas estimadas fueron más precisas. La CPU y el tiempo transcurrido fueron mejores (como se esperaba, ya que no tenía que calcular nada por fila).
  • No pude crear un índice filtrado o estadísticas filtradas en la columna calculada (debido a que se estaba calculando), incluso si era PERSISTED:-(

1
Gracias por la exhaustiva comparación entre persistente y no. Es bueno saber que incluso si la columna computada persistente tiene sus ventajas, la no persistencia puede ser una ganancia muy rápida con muy poca sobrecarga en algunos casos donde las estadísticas sobre una expresión son beneficiosas.
Geoff Patterson el
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.