Estimación de la cardinalidad para el operador LIKE (variables locales)


24

Tenía la impresión de que al utilizar el LIKEoperador en todos los escenarios optimizados para situaciones desconocidas, tanto el CE heredado como el nuevo usan una estimación del 9% (suponiendo que las estadísticas relevantes estén disponibles y el optimizador de consultas no tenga que recurrir a conjeturas de selectividad).

Al ejecutar la consulta a continuación en la base de datos de crédito, obtengo diferentes estimaciones según las diferentes CE. Bajo el nuevo CE recibo un estimado de 900 filas que esperaba, bajo el legado CE recibo un estimado de 241.416 y no puedo entender cómo se deriva este estimado. ¿Alguien puede arrojar alguna luz?

-- New CE (Estimate = 900)
DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM [Credit].[dbo].[member]
WHERE [lastname] LIKE @LastName;

-- Forcing Legacy CE (Estimate = 241.416)
DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM [Credit].[dbo].[member]
WHERE [lastname] LIKE @LastName
OPTION (
QUERYTRACEON 9481,
QUERYTRACEON 9292,
QUERYTRACEON 9204,
QUERYTRACEON 3604
);

En mi caso, ya tengo la base de datos de crédito establecida en el nivel de compatibilidad 120, por lo tanto, en la segunda consulta estoy usando indicadores de seguimiento para forzar el CE heredado y también para proporcionar información sobre qué estadísticas utiliza / considera el optimizador de consultas. Puedo ver que se están utilizando las estadísticas de columna sobre 'apellido', pero aún no puedo entender cómo se deriva la estimación de 241.416.

No pude encontrar nada en línea que no sea este artículo de Itzik Ben-Gan , que dice "Al usar el predicado LIKE en todos los escenarios desconocidos, tanto el CE heredado como el nuevo usan una estimación del 9 por ciento". La información en esa publicación parece ser incorrecta.

Respuestas:


28

La suposición LIKE en su caso se basa en:

  • G: La conjetura estándar del 9% ( sqllang!x_Selectivity_Like)
  • M: Un factor de 6 (número mágico)
  • D: Longitud de datos promedio en bytes (de las estadísticas), redondeada al entero

Específicamente, sqllang!CCardUtilSQL7::ProbLikeGuessutiliza:

Selectivity (S) = G / M * LOG(D)

Notas:

  • El LOG(D)término se omite si Destá entre 1 y 2.
  • Si Des menor que 1 (incluso para NULLestadísticas faltantes o ):
    D = FLOOR(0.5 * maximum column byte length)

Este tipo de peculiaridad y complejidad es bastante típico del CE original.

En el ejemplo de la pregunta, la longitud promedio es 5 (5.6154 de DBCC SHOW_STATISTICSredondeado hacia abajo):

Estimación = 10,000 * (0.09 / 6 * LOG (5)) = 241.416

Otros valores de ejemplo:

 D   = Estimación usando la fórmula para S
 15 = 406,208
 14 = 395.859
 13 = 384.742
 12 = 372.736
 11 = 359,684
 10 = 345,388
 09 = 329.584
 08 = 311.916
 07 = 291.887
 06 = 268.764
 05 = 241.416
 04 = 207.944
 03 = 164.792
 02 = 150.000 (REGISTRO no utilizado)
 01 = 150.000 (REGISTRO no utilizado)
 00 = 291.887 (LOG 7) / * FLOOR (0.5 * 15) [15 ya que el apellido es varchar (15)] * /

Banco de pruebas

DECLARE
    @CharLength integer = 5, -- Set length here
    @Counter integer = 1;

CREATE TABLE #T (c1 varchar(15) NULL);

-- Add 10,000 rows
SET NOCOUNT ON;
SET STATISTICS XML OFF;

BEGIN TRANSACTION;
WHILE @Counter <= 10000
BEGIN
    INSERT #T (c1) VALUES (REPLICATE('X', @CharLength));
    SET @Counter = @Counter + 1;
END;
COMMIT TRANSACTION;

SET NOCOUNT OFF;
SET STATISTICS XML ON;

-- Test query
DECLARE @Like varchar(15);
SELECT * FROM #T AS T 
WHERE T.c1 LIKE @Like;

DROP TABLE #T;

15

Probé en SQL Server 2014 con el CE heredado y tampoco obtuve un 9% como estimación de cardinalidad. No pude encontrar nada preciso en línea, así que hice algunas pruebas y encontré un modelo que se ajusta a todos los casos de prueba que probé, pero no puedo estar seguro de que esté completo.

En el modelo que encontré, la estimación se deriva del número de filas en la tabla, la longitud de clave promedio de las estadísticas para la columna filtrada y, a veces, la longitud del tipo de datos de la columna filtrada. Hay dos fórmulas diferentes utilizadas para la estimación.

Si FLOOR (longitud de clave promedio) = 0, la fórmula de estimación ignora las estadísticas de la columna y crea una estimación basada en la longitud del tipo de datos. Solo probé con VARCHAR (N), por lo que es posible que haya una fórmula diferente para NVARCHAR (N). Aquí está la fórmula para VARCHAR (N):

(estimación de fila) = (filas en la tabla) * (-0.004869 + 0.032649 * log10 (longitud del tipo de datos))

Esto tiene un ajuste muy agradable, pero no es perfectamente preciso:

primer gráfico de fórmula

El eje x es la longitud del tipo de datos y el eje y es el número de filas estimadas para una tabla con 1 millón de filas.

El optimizador de consultas usaría esta fórmula si no tuviera estadísticas en la columna o si la columna tiene suficientes valores NULL para conducir la longitud de clave promedio a menos de 1.

Por ejemplo, suponga que tiene una tabla con 150k filas con filtrado en un VARCHAR (50) y sin estadísticas de columna. La predicción de la estimación de la fila es:

150000 * (-0.004869 + 0.032649 * log10 (50)) = 7590.1 filas

SQL para probarlo:

CREATE TABLE X_CE_LIKE_TEST_1 (
STRING VARCHAR(50)
);

CREATE STATISTICS X_STAT_CE_LIKE_TEST_1 ON X_CE_LIKE_TEST_1 (STRING) WITH NORECOMPUTE;

WITH
    L0 AS (SELECT 1 AS c UNION ALL SELECT 1),
    L1 AS (SELECT 1 AS c FROM L0 A CROSS JOIN L0 B),
    L2 AS (SELECT 1 AS c FROM L1 A CROSS JOIN L1 B),
    L3 AS (SELECT 1 AS c FROM L2 A CROSS JOIN L2 B),
    L4 AS (SELECT 1 AS c FROM L3 A CROSS JOIN L3 B CROSS JOIN L2 C),
    NUMS AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS NUM FROM L4)  
    INSERT INTO X_CE_LIKE_TEST_1 WITH (TABLOCK) (STRING)
    SELECT TOP (150000) 'ZZZZZ'
    FROM NUMS
    ORDER BY NUM;

DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM X_CE_LIKE_TEST_1
WHERE STRING LIKE @LastName;

SQL Server proporciona un recuento de filas estimado de 7242.47, que es un poco cerrado.

Si FLOOR (longitud de clave promedio)> = 1, se utiliza una fórmula diferente que se basa en el valor de FLOOR (longitud de clave promedio). Aquí hay una tabla de algunos de los valores que probé:

1    1.5%
2    1.5%
3    1.64792%
4    2.07944%
5    2.41416%
6    2.68744%
7    2.91887%
8    3.11916%
9    3.29584%
10   3.45388%

Si FLOOR (longitud de clave promedio) <6, use la tabla anterior. De lo contrario, use la siguiente ecuación:

(estimación de fila) = (filas en la tabla) * (-0.003381 + 0.034539 * log10 (PISO (longitud de clave promedio)))

Este tiene un mejor ajuste que el otro, pero aún no es perfectamente preciso.

segundo gráfico de fórmula

El eje x es la longitud promedio de la clave y el eje y es el número de filas estimadas para una tabla con 1 millón de filas.

Para dar otro ejemplo, suponga que tiene una tabla con 10k filas con una longitud de clave promedio de 5.5 para las estadísticas en la columna filtrada. La estimación de la fila sería:

10000 * 0.241416 = 241.416 filas.

SQL para probarlo:

CREATE TABLE X_CE_LIKE_TEST_2 (
STRING VARCHAR(50)
);

WITH
    L0 AS (SELECT 1 AS c UNION ALL SELECT 1),
    L1 AS (SELECT 1 AS c FROM L0 A CROSS JOIN L0 B),
    L2 AS (SELECT 1 AS c FROM L1 A CROSS JOIN L1 B),
    L3 AS (SELECT 1 AS c FROM L2 A CROSS JOIN L2 B),
    L4 AS (SELECT 1 AS c FROM L3 A CROSS JOIN L3 B CROSS JOIN L2 C),
    NUMS AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS NUM FROM L4)  
    INSERT INTO X_CE_LIKE_TEST_2 WITH (TABLOCK) (STRING)
    SELECT TOP (10000) 
    CASE 
      WHEN NUM % 2 = 1 THEN REPLICATE('Z', 5) 
      ELSE REPLICATE('Z', 6)
    END
    FROM NUMS
    ORDER BY NUM;

CREATE STATISTICS X_STAT_CE_LIKE_TEST_2 ON X_CE_LIKE_TEST_2 (STRING) 
WITH NORECOMPUTE, FULLSCAN;

DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM X_CE_LIKE_TEST_2
WHERE STRING LIKE @LastName;

La estimación de la fila es 241.416, que coincide con lo que tiene en la pregunta. Habría algún error si usara un valor que no está en la tabla.

Los modelos aquí no son perfectos, pero creo que ilustran bastante bien el comportamiento general.

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.