ROW_NUMBER () OVER (PARTICIÓN POR B, A ORDEN POR C) no usa índice en (A, B, C)

12

Considere estas dos funciones:

ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C)

ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C)

Por lo que yo entiendo, producen exactamente el mismo resultado. En otras palabras, el orden en el que enumera las columnas en la PARTITION BYcláusula no importa.

Si hay un índice en (A,B,C)espera que el optimizador use este índice en ambas variantes.

Pero, sorprendentemente, el optimizador decidió hacer una ordenación adicional explícita en la segunda variante.

Lo he visto en SQL Server 2008 Standard y SQL Server 2014 Express.

Aquí hay un script completo que usé para reproducirlo.

Probado en Microsoft SQL Server 2014 - 12.0.2000.8 (X64) 20 de febrero de 2014 20:04:26 Copyright (c) Microsoft Corporation Express Edition (64 bits) en Windows NT 6.1 (Build 7601: Service Pack 1)

y Microsoft SQL Server 2014 (SP1-CU7) (KB3162659) - 12.0.4459.0 (X64) 27 de mayo de 2016 15:33:17 Copyright (c) Microsoft Corporation Express Edition (64 bits) en Windows NT 6.1 (Build 7601: Servicio Paquete 1)

con el estimador de cardinalidad antiguo y el nuevo utilizando OPTION (QUERYTRACEON 9481)y OPTION (QUERYTRACEON 2312).

Configurar tabla, índice, datos de muestra

CREATE TABLE [dbo].[T](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [A] [int] NOT NULL,
    [B] [int] NOT NULL,
    [C] [int] NOT NULL,
    CONSTRAINT [PK_T] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, 
STATISTICS_NORECOMPUTE = OFF, 
IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS = ON, 
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_ABC] ON [dbo].[T]
(
    [A] ASC,
    [B] ASC,
    [C] ASC
)WITH (PAD_INDEX = OFF, 
STATISTICS_NORECOMPUTE = OFF, 
SORT_IN_TEMPDB = OFF, 
DROP_EXISTING = OFF, 
ONLINE = OFF, 
ALLOW_ROW_LOCKS = ON, 
ALLOW_PAGE_LOCKS = ON)
GO

INSERT INTO [dbo].[T] ([A],[B],[C]) VALUES
(10, 20, 30),
(10, 21, 31),
(10, 21, 32),
(10, 21, 33),
(11, 20, 34),
(11, 21, 35),
(11, 21, 36),
(12, 20, 37),
(12, 21, 38),
(13, 21, 39);

Consultas

SELECT -- AB
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
FROM T
ORDER BY C
OPTION(RECOMPILE);

SELECT -- BA
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
FROM T
ORDER BY C
OPTION(RECOMPILE);

SELECT -- both
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
    ,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
FROM T
ORDER BY C
OPTION(RECOMPILE);

Planes de ejecucion

PARTICIÓN POR A, B

AB

PARTICIÓN POR B, A

licenciado en Letras

Ambos

ambos

Como puede ver, el segundo plan tiene una clasificación adicional. Ordena por B, A, C. El optimizador, aparentemente, no es lo suficientemente inteligente como para darse cuenta de que PARTITION BY B,Aes lo mismo PARTITION BY A,By vuelve a ordenar los datos.

Curiosamente, la tercera consulta tiene ambas variantes ROW_NUMBER, ¡y no hay una clasificación adicional! El plan es el mismo que para la primera consulta. (El Proyecto de secuencia tiene una expresión adicional en la Lista de salida para la columna adicional, pero no tiene una Clasificación adicional). Entonces, en este caso más complicado, el optimizador parecía ser lo suficientemente inteligente como para darse cuenta de que PARTITION BY B,Aes lo mismo PARTITION BY A,B.

En la primera y tercera consulta, el operador de exploración de índice tiene la propiedad Ordenado: Verdadero, en la segunda consulta es Falso.

Aún más interesante, si reescribo la tercera consulta de esta manera (intercambia dos columnas):

SELECT -- both
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
    ,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
FROM T
ORDER BY C
OPTION(RECOMPILE);

¡entonces el Sort extra aparece de nuevo!

¿Alguien podría arrojar algo de luz? ¿Qué está pasando en el optimizador aquí?

Vladimir Baranov
fuente
Comentarios archivados .
Paul White 9

Respuestas:

2

Parece que no hay una buena "respuesta" definitiva a la pregunta "qué está pasando en el optimizador", a menos que usted sea su desarrollador y conozca sus aspectos internos.

Armaré los comentarios aquí.

En general, parece que sería demasiado duro llamarlo un error, porque el resultado final de la consulta es correcto. En algunos casos, el plan de ejecución simplemente no es óptimo. ypercubeᵀᴹ , Martin Smith y Aaron Bertrand lo llaman "optimización perdida".

  • Parece GROUP BY a,by GROUP BY b,aproduce planes idénticos pero PARTITION BYno puede usar la misma transformación

  • También faltan otras optimizaciones en las que las funciones de ventana con la misma especificación de ventana pueden tener una operación de clasificación adicional si están separadas en la lista de selección por una con una especificación diferente.

  • Sí, esto parece otra optimización perdida, y hay muchas de esas. El optimizador está escrito por humanos y no es perfecto.


Hay un artículo un poco relacionado Índices descendentes. Índice de ordenamiento, paralelismo y cálculos de clasificación por Itzik Ben-Gan. Allí Itzik analiza los índices descendentes y también da un ejemplo de cómo la dirección de la definición del índice afecta las funciones de la ventana con particiones. Muestra ejemplos de consultas y planes generados con ROW_NUMBERun operador de clasificación adicional que el optimizador podría haber evitado.


Para mí, el resultado práctico sería tener presente esta peculiaridad del optimizador. Cuando utilice las PARTITION BYfunciones en la ventana, siempre trate de hacer coincidir el orden en el que enumera las columnas PARTITION BYcon el orden en que aparecen en el índice. Aunque no debería importar.

Otro lado de esta precaución es cuando revisa sus índices y decide intercambiar algunas columnas en la definición del índice. Tenga en cuenta que puede afectar inadvertidamente algunas consultas existentes que aparentemente no deberían verse afectadas. Así es como noté esta peculiaridad del optimizador.

Si no lo hace, es posible que el optimizador no pueda usar el índice en todo su potencial. Incluso si el optimizador elige un plan óptimo, dicho plan puede cambiar a menos óptimo con un cambio inocente a la consulta, como cambiar el orden de las columnas en la SELECTdeclaración.

Vladimir Baranov
fuente