Comparando duas consultas no SQL Server 2012

14

Estou comparando duas consultas no SQL Server 2012. O objetivo é usar todas as informações pertinentes disponíveis no otimizador de consultas ao escolher a melhor consulta. Ambas as consultas produzem os mesmos resultados; o número máximo de pedidos para todos os clientes.

A limpeza do buffer pool foi feita antes de executar cada consulta com FREEPROCCACHE e DROPCLEANBUFFERS

Usando as informações fornecidas abaixo, qual consulta é a melhor escolha?

-- Query 1 - return the maximum order id for a customer
SELECT orderid, custid
FROM Sales.Orders AS O1
WHERE orderid = (SELECT MAX(O2.orderid)
                 FROM Sales.Orders AS O2
                 WHERE O2.custid = O1.custid);


-- Query 2 - return the maximum order id for a customer
SELECT MAX(orderid), custid
FROM Sales.Orders AS O1
group by custid
order by custid

TEMPO ESTATÍSTICO

Consulta 1 TEMPO ESTATÍSTICO: tempo de CPU = 0ms, tempo decorrido = 24 ms

Consulta 2 TEMPO ESTATÍSTICO: tempo de CPU = 0 ms, tempo decorrido = 23 ms

ESTATÍSTICAS IO

Consulta 1 ESTATÍSTICA IO: Tabela 'Pedidos'. Contagem de varreduras 1, leituras lógicas 5, leituras físicas 2, leituras antecipadas 0, leituras lógicas lob 0, leituras lógicas lob 0, leituras físicas lob 0, leituras antecipadas lob.

Consulta 2 ESTATÍSTICAS IO: Tabela 'Pedidos'. Contagem de varreduras 1, leituras lógicas 4, leituras físicas 1, leituras antecipadas 8, leituras lógicas 0, lob leituras físicas 0, leituras físicas lob 0, leituras antecipadas lob 0.

Planos de Execução

insira a descrição da imagem aqui

Propriedades SELECT Consulta 1

insira a descrição da imagem aqui

Propriedades SELECT Consulta 2

insira a descrição da imagem aqui

Conclusões:

Consulta 1

  1. Custo do lote 48%
  2. Leituras lógicas 5
  3. Leituras físicas 2
  4. Leituras antecipadas: 0
  5. Tempo de CPU: 0ms
  6. Tempo decorrido 24ms
  7. Custo estimado da subárvore: 0.0050276
  8. CompileCPU: 2
  9. CompileMemory: 384
  10. CompileTime: 2

Consulta 2

  1. Custo do lote 52%
  2. Leituras lógicas 4
  3. Listas Physcial 1
  4. Leituras de leitura antecipada: 8
  5. Tempo de CPU 0
  6. Tempo decorrido 23ms
  7. Custo estimado da subárvore: 0.0054782
  8. CompileCPU: 0
  9. CompileMemory: 192
  10. CompileTime: 0

Pessoalmente, embora a Consulta 2 tenha um custo de lote mais alto, de acordo com o plano gráfico, acho mais eficaz que a Consulta 1. Isso porque a consulta 2 requer leituras menos lógicas, tem um tempo decorrido um pouco menor, os valores compilecpu, compilememory e compiletime são mais baixo. as leituras de read-ahead são 8 para a consulta 2 e 0 para a consulta 1.

Atualização 12:03

Definição de índice clusterizado

ALTER TABLE [Sales].[Orders] ADD  CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED 
(
    [orderid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

Índice não clusterizado idx_nc_custid

CREATE NONCLUSTERED INDEX [idx_nc_custid] ON [Sales].[Orders]
(
    [custid] 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) ON [PRIMARY]
GO
Craig Efrein
fonte
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
Paul White 9

Respostas:

10

Adoro sua abordagem para uma consideração cuidadosa para consultar opções de ajuste e revisão. Eu gostaria que mais desenvolvedores fizessem isso. Uma precaução seria: sempre teste com muitas linhas, observando as leituras lógicas, esta é uma tabela pequena. Tente gerar uma carga de amostra e execute a consulta novamente. Um pequeno problema: na sua consulta superior, você não está solicitando um pedido; na consulta inferior, você é. Você deve compará-los e contrastá-los com os pedidos.

Acabei de criar rapidamente uma tabela SalesOrders com 200.000 pedidos de vendas - ainda não muito grande com a imaginação. E executou as consultas com o ORDER BY em cada um. Eu também brinquei um pouco com os índices.

Sem índice clusterizado no OrderID, apenas um índice não clusterizado no CustID. A segunda consulta teve um desempenho superior. Especialmente com o pedido incluído em cada um. Havia duas vezes mais leituras na primeira consulta do que na segunda e as porcentagens de custo foram de 67% / 33% entre as consultas.

Com um índice em cluster no OrderID e um índice não em cluster apenas no CustID. Eles executaram em uma velocidade semelhante e no mesmo número exato de leituras.

Então, sugiro que você aumente o número de linhas e faça mais alguns testes. Mas minha análise final sobre suas consultas -

Você pode achar que eles se comportam de maneira mais semelhante ao que você imagina quando aumenta as linhas; portanto, lembre-se dessa advertência e teste dessa maneira.

Se tudo o que você deseja retornar é o número máximo de pedidos para cada cliente, e você deseja determinar que, pelo número de pedidos, o maior número de pedidos, a segunda consulta dessas duas é a melhor maneira de sair da minha cabeça - é um pouco mais simples e, embora um pouco mais caro, com base no custo da subárvore, é uma declaração mais rápida e fácil de decifrar. Se você pretende adicionar outras colunas ao seu conjunto de resultados algum dia? Em seguida, a primeira consulta permite que você faça isso.

Atualizado: um dos seus comentários em sua pergunta foi:

Lembre-se de que encontrar a melhor consulta nesta pergunta é um meio de refinar as técnicas usadas para compará-las.

Mas a melhor maneira de fazer esse teste com mais dados - sempre garante que você tenha dados consistentes com a produção e a produção futura esperada. Os planos de consulta começam a procurar dados quando você fornece mais linhas para as tabelas e tenta manter a distribuição que você esperaria na produção. E preste atenção em coisas como incluir Order By ou não, aqui não acho que faça muita diferença no final, mas ainda vale a pena investigar.

Sua abordagem para comparar esse nível de detalhe e dados é boa. Os custos das subárvores são arbitrários e sem sentido, principalmente, mas ainda valem pelo menos uma comparação entre edições / alterações ou mesmo entre consultas. Observar as estatísticas de tempo e o pedido de veiculação é bastante importante, assim como observar o plano para qualquer coisa que pareça inadequada para o tamanho dos dados com os quais você está trabalhando e o que está tentando fazer.

Mike Walsh
fonte
Olá novamente, obrigado por seus pontos sobre o uso de volumes maiores de dados. Esta não é a primeira vez que alguém menciona. A última vez que foi considerada a possível fragmentação das divisões de página. Na sua amostra de 200.000 linhas, você verificou a fragmentação?
Craig Efrein
Bem, no meu pequeno exemplo rápido de 200 mil linhas, eu não estava focado na fragmentação, não. Mas do jeito que eu fiz isso, não haveria. Eu criei a tabela, preenchai e fiz os índices, então eles foram criados recentemente. E isso não mudará a abordagem de olhar para os planos de consulta, que parece ser a questão principal. O volume de dados é grande - muito grande - ao analisar os planos de consulta com precisão. Eu já vi casos em que parecia ótimo no desenvolvimento (com 1 a 10 linhas) e era horrível em prod com dados reais. Mas sua abordagem é bom e espero que esta informação ea conversa nos comentários ajuda
Mike Walsh
Como estamos agrupando por custid, como você tornou os valores custid aleatórios o suficiente? Uma coisa que lembro das minhas leituras é a importância de valores distintos. Se a custid tivesse apenas um pequeno número de clientes distintos, o custo para o fluxo agregado seria irreal.
Craig Efrein
Acabei de usar a função RAND para criar 100 clientes e atribuir aleatoriamente um a cada ID do pedido. Estava fazendo uma verificação rápida. :)
Mike Walsh
Obrigado Mike por toda a sua ajuda. Uma última pergunta, no entanto. Nas telas de propriedades SELECT do Plano de Execução em 2012 que forneço na minha pergunta, em quais valores você presta atenção?
Craig Efrein