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 BY
clá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
PARTICIÓN POR B, A
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,A
es lo mismo PARTITION BY A,B
y 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,A
es 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í?
fuente
Respuestas:
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".
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_NUMBER
un 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 BY
funciones en la ventana, siempre trate de hacer coincidir el orden en el que enumera las columnasPARTITION BY
con 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
SELECT
declaración.fuente