¿Cambios en las estimaciones de predicados que contienen SUBSTRING () en SQL Server 2016?


13

¿Existe alguna documentación o investigación sobre los cambios en SQL Server 2016 sobre cómo se estima la cardinalidad para los predicados que contienen SUBSTRING () u otras funciones de cadena?

La razón por la que pregunto es que estaba mirando una consulta cuyo rendimiento se degradó en el modo de compatibilidad 130 y la razón se relacionó con un cambio en la estimación del número de filas que coinciden con una cláusula WHERE que contenía una llamada a SUBSTRING (). Corrigí el problema con una reescritura de consultas, pero me pregunto si alguien conoce alguna documentación sobre cambios en esta área en SQL Server 2016.

El código de demostración está debajo. Las estimaciones son muy cercanas en este caso de prueba, pero la precisión varía según los datos.

En el caso de prueba, en el nivel de compatibilidad 120, SQL Server parece estar usando el histograma para la estimación, mientras que en el nivel de compatibilidad 130 SQL Server parece estar asumiendo que un 10% fijo de la tabla coincide.

CREATE DATABASE MyStringTestDB;
GO
USE MyStringTestDB;
GO
DROP TABLE IF EXISTS dbo.StringTest;
CREATE TABLE dbo.StringTest ( [TheString] varchar(15) );
GO
INSERT INTO dbo.StringTest
VALUES
( 'Y5_CLV' );
INSERT INTO dbo.StringTest
VALUES
( 'Y5_EG3' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_NE' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_PQT' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_T2V' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_TT4' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_ZKK' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_LW6' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_QO3' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_TZ7' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_UZZ' );

CREATE CLUSTERED INDEX IX_Clustered ON dbo.StringTest (TheString);

/* 
Uses fixed % for estimate; 1.1 rows estimated in this case.
    Plan for computation:
        CSelCalcFixedFilter (0.1) <----
            Selectivity: 0.1
*/
ALTER DATABASE MyStringTestDB SET compatibility_level = 130;
GO
SELECT * 
FROM dbo.StringTest 
WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);

/* 
Uses histogram to get estimate of 1
 CSelCalcPointPredsFreqBased <----
      Distinct value calculation:
          CDVCPlanLeaf
              0 Multi-Column Stats, 1 Single-Column Stats, 0 Guesses
      Individual selectivity calculations:
          (none)
    Loaded histogram for column QCOL: [DBA].[dbo].[StringTest].TheString from stats with id 1
*/
ALTER DATABASE MyStringTestDB SET compatibility_level = 120;
GO
SELECT * 
FROM dbo.StringTest 
WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);

/*
-- Simpler rewrite; works fine in both compat levels and gets better estimate.
SELECT * 
FROM dbo.StringTest 
WHERE TheString LIKE 'ZZ[_]%'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);
*/

1
No estoy seguro acerca de la pregunta en particular, pero si las Y5_EG3cadenas son solo códigos y siempre mayúsculas, entonces siempre puede intentar especificar una intercalación binaria Latin1_General_100_BIN2, lo que debería mejorar la velocidad en las operaciones de filtrado. Simplemente agregue COLLATE Latin1_General_100_BIN2a la CREATE TABLEdeclaración, justo después de varchar(15). Me gustaría saber si también afectó la generación / estimación del plan.
Solomon Rutzky

Respuestas:


8

No conozco ninguna documentación. Investigué esto y hice algunas observaciones, sin embargo, que son demasiado largas para un comentario.

La estimación del 10% no siempre es una degradación. Toma el siguiente ejemplo.

TRUNCATE TABLE dbo.StringTest

INSERT INTO dbo.StringTest
SELECT TOP (1000000) 'ZZ_' + LEFT(NEWID(), 12)
FROM   master..spt_values v1,
       master..spt_values v2;

y la WHEREcláusula en tu pregunta.

WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'

La tabla contiene un millón de filas. Todos ellos coinciden con el predicado. Bajo el nivel de compatibilidad 130, la suposición del 10% arroja una estimación de 100,000. Menos de 120 las filas estimadas es 1.03913.

El comportamiento 120 usa el histograma pero solo para obtener el número de filas distintas. El vector de densidad en mi caso muestra 1.039131E-06 y esto se multiplica por la cardinalidad de la tabla para obtener el recuento de filas estimado. De hecho, todos los valores son diferentes, pero todos coinciden con el predicado.

El seguimiento del query_optimizer_estimate_cardinalityevento extendido muestra que menos de 130 hay dos <StatsCollection Name="CStCollFilter"eventos diferentes . El primero estima 100,000. El segundo carga el histograma y usa CSelCalcPointPredsFreqBased / DistinctCountCalculator para obtener la estimación de 1.04. Este segundo resultado parece no utilizado.

El comportamiento que observó no se aplica de manera consistente en 130. Agregué ORDER BY TheStringesperando que esto sea una clara victoria para el estimador de 130, ya que los 120 luchan con una concesión de memoria para una fila, pero este cambio menor fue suficiente para reducir las filas estimadas a 1.03913 en el caso 130 también.

La OPTION (QUERYRULEOFF SelectToFilter)suma revierte la estimación que ingresa en la clasificación a 100,000 pero la concesión de memoria no aumenta y las estimaciones que salen de la clasificación todavía se basan en los valores distintos de la tabla.

ingrese la descripción de la imagen aquí

De manera similar, ajustar el umbral de costo para el paralelismo para que la consulta obtenga un plan paralelo fue suficiente en el caso 130 para volver a la estimación más baja. Agregar QUERYTRACEON 8757también causa la estimación más baja. Parece que la estimación del 10% solo se retiene para planes triviales.

Su propuesta reescribe con

WHERE TheString LIKE 'ZZ[_]%'

Muestra estimaciones muy superiores a ambos. La salida para esto es

  CSelCalcTrieBased

      Column: QCOL: [MyStringTestDB].[dbo].[StringTest].TheString

Mostrando que utilizó intentos . Más información sobre esto está en la sección de estadísticas de resumen de cadena justo arriba de aquí .

Sin embargo, no es lo mismo que su consulta original. Como _ahora se supone que la primera instancia de siempre es el tercer personaje en lugar de ser encontrado dinámicamente.

Si esta suposición está codificada en su consulta original

 WHERE SUBSTRING(TheString, 1, 3) = 'ZZ_'

El método de estimación cambia CSelCalcHistogramComparison(INTERVAL)y las filas estimadas se vuelven precisas.

Es capaz de convertir eso en un rango

WHERE TheString >=  'ZZ_' AND TheString < ???

y use el histograma para estimar el número de filas con valores en ese rango.

Sin embargo, esto solo se aplica a la estimación de cardinalidad. LIKEes preferible ya que puede usar una búsqueda de rango en tiempo de ejecución. SUBSTRING(TheString, 1, 3)o LEFT(TheString, 3)no puedo

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.