Ordem de campo em uma ordem de índice composto com campos de alta seletividade e baixa seletividade

11

Eu tenho uma tabela do SQL Server com mais de 3 bilhões de linhas. Uma das minhas consultas leva um tempo extremamente longo, por isso estou pensando em otimizá-las. A consulta fica assim:

SELECT [Enroll_Date]
      ,Count(*) AS [Record #]
      ,Count(Distinct UserID) AS [User #]
  FROM UserTable
  GROUP BY [Enroll_Date]

O [Enroll_Date] é uma coluna de baixa seletividade com menos de 50 valores possíveis, enquanto a coluna UserID é uma coluna de alta seletividade com mais de 200 milhões de valores distintos. Com base em minha pesquisa, acredito que devo criar um índice composto não agrupado nessas duas colunas e, em teoria, a coluna de alta seletividade deve ser a primeira coluna. Mas não tenho certeza no meu caso, isso funcionaria porque estou usando a coluna de baixa seletividade no grupo por cláusula.

Esta tabela não possui índice clusterizado.

Thinkinger
fonte
Você pode postar o xml do plano de execução real (use pastebin e vincule-o aqui)? Qual versão do servidor sql você está usando?
Kin Shah
3
O índice com a coluna altamente seletiva primeiro será inútil para a consulta específica.
ypercubeᵀᴹ
É uma boa prática usar a coluna de maior seletividade como a primeira coluna-chave em um índice (normalmente). Nesse cenário, como você adivinhou, isso não ajuda em nada. Você pode precisar de dois índices! O que acontece quando você usa o register_date primeiro e o user_id segundo?
21316 Paulbarbin

Respostas:

12

Como alternativa à solução do @ AaronBertrand (se você não pode ou não deseja criar uma exibição indexada), recomendo que você crie um índice (Enroll_Date, UserID). Se esse tipo de pergunta for muito comum na sua tabela, provavelmente esse deve ser o seu índice em cluster.

Geralmente, eu não recomendaria índices de alta seletividade como uma "melhor prática" geral, mas observe qual índice oferecerá o melhor desempenho para sua consulta.

Um índice (Enroll_Date, UserID)ativado fornecerá à sua consulta um plano de consulta altamente otimizado e sem bloqueio com os agregados de fluxo.

Fluxo de plano de consulta agregado

"Não-bloqueante" neste contexto significa que a consulta não precisa armazenar quantidades significativas de dados (como, por exemplo, uma agregação de classificação ou hash), o que significa que (a) começa a retornar linhas imediatamente e ( b) consome praticamente nenhuma memória de trabalho.

Daniel Hutmacher
fonte
Engraçado, com 4 segundos de diferença e a mesma resposta.
usr
11

A resposta de Aarons é uma ótima solução. Responderei à pergunta assumindo que você não deseja adotar essa abordagem.

A consulta que você postou geralmente será executada agrupando primeiro (Enroll_Date, UserID)e depois novamente (Enroll_Date). Essa otimização é nova no SQL Server 2012. Ela entra em vigor no caso de uma única COUNT DISTINCT.

Um índice nessas duas colunas na ordem específica (Enroll_Date, UserID)será suficiente para obter um plano eficiente que canalize uma varredura de índice em dois agregados de fluxo consecutivos. A ordem oposta não permitiria esse plano.

Portanto, use o pedido (Enroll_Date, UserID). Você não tem escolha aqui.

usr
fonte
5 segundos de intervalo e a mesma solução. Bem jogado, senhor. :)
Daniel Hutmacher
@DanielHutmacher OMG, conseguiremos quase igualar nossos posts pela terceira vez ?! +1 para você! Como não pude votar de uma resposta idêntica?
usr
Falha na matriz. :)
Daniel Hutmacher 17/02
Muito obrigado. Estou criando o índice e publicarei a melhoria depois que ela estiver concluída. A versão do servidor é o Microsoft SQL Server 2008 R2 na AWS, mas acho que ainda é o único escolhido independentemente.
Thinkinger
@Thinkinger no caso de você não está aceitando Aarons aproximar você tem uma escolha difícil :)
usr
11

Parece um cenário ideal para uma exibição indexada, que permite pagar cálculos e agregações no momento da gravação, em vez do tempo da consulta.

CREATE VIEW dbo.MyIndexedView
WITH SCHEMABINDING
AS 
  SELECT Enroll_Date, UserID, RawCount = COUNT_BIG(*)
  FROM dbo.UserTable
  GROUP BY Enroll_Date, UserID;
GO

CREATE UNIQUE CLUSTERED INDEX CIX_miv ON dbo.MyIndexedView(Enroll_Date, UserID);

Isso levará algum tempo para criar e, é claro, exigirá manutenção em todas as operações DML, como um índice na tabela base.

Agora, a consulta nessa visualização seria bastante semelhante - cada linha na visualização agora representa uma combinação de usuário / data distinta, para que o número possa ser calculado por uma única COUNT (*), enquanto o número total de linhas na tabela base é já agregado parcialmente para você, agora você só precisa adicioná-los usando SUM por data:

SELECT Enroll_Date, 
  [Record #] = SUM(RawCount),
  [User #] = COUNT(*)
FROM dbo.MyIndexedView WITH (NOEXPAND)
GROUP BY Enroll_Date; 

Adicionado NOEXPAND dica, depois de lembrar isso e isso .

Posso dizer sem dúvida que essa consulta será mais rápida que a atual (mas não em quanto), exceto nos casos raros em que você tenha exatamente um usuário para cada data (nesse caso, a mesma quantidade de dados terá para serem lidas) e as colunas que conhecemos são as únicas colunas no índice da tabela base. Se esse aumento de desempenho no momento da leitura vale o trabalho extra que afetará a parte de gravação da sua carga de trabalho é algo que não podemos dizer a você - você precisará testá-lo para medir o trade-off (nenhum índice é gratuito).

E se você costuma usar as mesmas cláusulas WHERE comuns em relação ao Enroll_Date para intervalos específicos e bem definidos (por exemplo, o trimestre ou ano atual), você pode adicionar índices filtrados correspondentes que reduzam ainda mais a E / S (mas sempre há um troca).

Você também pode considerar colocar um índice em cluster na tabela base. Esse não parece ser um daqueles casos de uso muito raros que se beneficiam de uma pilha.

Aaron Bertrand
fonte
Acabei de confirmar com a nossa TI e parece que não consigo criar esse tipo de visão. Mas ainda apricie seu conselho, e isso ajudará outras pessoas que podem usá-lo.
Thinkinger
1
Sua equipe de TI acha que há uma diferença significativa entre uma exibição indexada e índices adicionais ou diferentes na tabela base? Não sendo combativo, apenas curioso, porque muitas pessoas têm conceitos errados sobre visualizações indexadas. Gosto de pensar neles como um índice agrupado adicional e mais fino na tabela, mas com menos linhas.
Aaron Bertrand
@Thinkinger também, as exibições indexadas não são apenas EE. A correspondência de exibição indexada é apenas para EE. Você pode direcioná-los diretamente usando NOEXPAND.
usr