ROW_NUMBER () OVER (PARTITION BY B, A ORDER BY C) não usa o índice em (A, B, C)

12

Considere estas duas funções:

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

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

Tanto quanto eu entendo, eles produzem exatamente o mesmo resultado. Em outras palavras, a ordem na qual você lista as colunas na PARTITION BYcláusula não importa.

Se houver um índice, (A,B,C)eu esperava que o otimizador usasse esse índice nas duas variantes.

Surpreendentemente, porém, o otimizador decidiu fazer uma classificação extra explícita na segunda variante.

Eu já vi isso no SQL Server 2008 Standard e no SQL Server 2014 Express.

Aqui está um script completo que eu usei para reproduzi-lo.

Tentei no Microsoft SQL Server 2014 - 12.0.2000.8 (X64) 20 de fevereiro de 2014 20:04:26 Direitos autorais (c) Microsoft Corporation Express Edition (64 bits) no Windows NT 6.1 (Build 7601: Service Pack 1)

e Microsoft SQL Server 2014 (SP1-CU7) (KB3162659) - 12.0.4459.0 (X64) 27 de maio de 2016 15:33:17 Copyright (c) Microsoft Corporation Express Edition (64 bits) no Windows NT 6.1 (Build 7601: Service Embalagem 1)

com o antigo e o novo estimador de cardinalidade usando OPTION (QUERYTRACEON 9481)e OPTION (QUERYTRACEON 2312).

Configurar tabela, índice, dados de amostra

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);

Planos de execução

PARTIÇÃO POR A, B

AB

PARTIÇÃO POR B, A

BA

Ambos

ambos

Como você pode ver, o segundo plano tem uma classificação extra. Ordena por B, A, C. O otimizador, aparentemente, não é inteligente o suficiente para perceber que PARTITION BY B,Aé o mesmo PARTITION BY A,Be reorganizar os dados.

Curiosamente, a terceira consulta possui as duas variantes ROW_NUMBERe não há Classificação extra! O plano é o mesmo da primeira consulta. (O Projeto de sequência tem expressão extra na lista de saída para a coluna extra, mas nenhuma classificação extra). Portanto, nesse caso mais complicado, o otimizador parecia inteligente o suficiente para perceber que PARTITION BY B,Aé o mesmo que PARTITION BY A,B.

Na primeira e terceira consultas, o operador Index Scan possui a propriedade Ordered: True, na segunda consulta é False.

Ainda mais interessante, se eu reescrever a terceira consulta como esta (troque duas colunas):

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);

então a classificação extra aparece novamente!

Alguém poderia lançar alguma luz? O que está acontecendo no otimizador aqui?

Vladimir Baranov
fonte
Comentários arquivados .
Paul White 9

Respostas:

2

Parece que não há uma "resposta" definitiva boa para a pergunta "o que está acontecendo no otimizador", a menos que você seja o desenvolvedor e conheça os internos.

Vou juntar os comentários aqui.

No geral, parece que seria muito duro chamá-lo de bug, porque o resultado final da consulta está correto. Em alguns casos, o plano de execução simplesmente não é o ideal. ypercubeᵀᴹ , Martin Smith e Aaron Bertrand chamam de "otimização perdida".

  • Parece GROUP BY a,be GROUP BY b,aproduz planos idênticos, mas PARTITION BYnão pode usar a mesma transformação

  • Também há outras otimizações ausentes, nas quais as funções de janela com a mesma especificação de janela podem ter uma operação de classificação extra se separadas na lista de seleção por uma com uma especificação diferente.

  • Sim, isso parece outra otimização perdida, e há muitas delas. O otimizador é escrito por humanos e não é perfeito


Há um artigo um pouco relacionado, índices descendentes. Ordenação de índices, paralelismo e cálculos de classificação por Itzik Ben-Gan. Lá, Itzik discute índices descendentes e também fornece um exemplo de como a direção da definição do índice afeta as funções da janela com partições. Ele mostra exemplos de consultas e planos gerados com ROW_NUMBERum operador de classificação extra que o otimizador poderia ter evitado.


Para mim, o resultado prático seria manter em mente essa peculiaridade de otimizador. Ao usar as PARTITION BYfunções da janela, tente sempre corresponder à ordem em que você lista as colunas emPARTITION BY com a ordem em que elas estão listadas no índice. Mesmo que isso não importe.

Outro lado dessa precaução é quando você revisa seus índices e decide trocar algumas colunas na definição de índice. Esteja ciente de que você pode afetar inadvertidamente algumas consultas existentes que aparentemente não devem ser afetadas. Foi assim que notei essa peculiaridade do otimizador.

Caso contrário, o otimizador poderá não conseguir usar o índice em todo o seu potencial. Mesmo que o otimizador escolha um plano ideal, esse plano poderá mudar para menos ideal com uma leve alteração inocente na consulta, como alterar a ordem das colunas na SELECTinstrução.

Vladimir Baranov
fonte