Database
 sql >> Base de Dados >  >> RDS >> Database

Uma abordagem para ajuste de índice - Parte 2


No meu último post, comecei a descrever o processo pelo qual passo ao ajustar consultas – especificamente quando descubro que preciso adicionar um novo índice ou modificar um já existente. Até este ponto, identificamos a consulta problemática, o índice de que preciso, quais índices existem atualmente na tabela e se esses índices estão sendo usados ​​ou não. Assim que tivermos esses dados, podemos passar para as próximas etapas do processo.
Etapa 5:o que usa um índice

Além de ver com que frequência um índice é usado (ou não), é útil saber quais consultas usar um índice, principalmente se eu quiser mesclá-lo com outro índice. Felizmente, Jonathan Kehayias já escreveu uma consulta para ajudar a identificar quais planos usam um índice específico. Sua versão pode ser usada para o cache do plano – o único desafio é que a informação é transitória, então você não pode capturar todas as consultas que usam um índice específico. O Query Store pode ajudar com isso – modifiquei a consulta dele para obter as mesmas informações dos planos no Query Store:
  SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
  DECLARE @IndexName AS NVARCHAR(128) = N'[IX_Sales_OrderLines_AllocatedStockItems]',
          @lb AS nchar(1) = N'[', @rb AS nchar(1) = N']';
 
  -- Make sure the name passed is appropriately quoted
  IF (LEFT(@IndexName, 1) <> @lb AND RIGHT(@IndexName, 1) <> @rb) SET @IndexName = QUOTENAME(@IndexName);
 
  --Handle the case where the left or right was quoted manually but not the opposite side
  IF LEFT(@IndexName, 1)  <> @lb SET @IndexName = @rb + @IndexName;
  IF RIGHT(@IndexName, 1) <> @rb SET @IndexName = @IndexName + @rb;
 
  ;WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')   
  SELECT
    stmt.value('(@StatementText)[1]', 'varchar(max)') AS SQL_Text,
    obj.value('(@Database)[1]', 'varchar(128)') AS DatabaseName,
    obj.value('(@Schema)[1]', 'varchar(128)') AS SchemaName,
    obj.value('(@Table)[1]', 'varchar(128)') AS TableName,
    obj.value('(@Index)[1]', 'varchar(128)') AS IndexName,
    obj.value('(@IndexKind)[1]', 'varchar(128)') AS IndexKind,
    query_plan
  FROM 	
  (
    SELECT query_plan
    FROM
    (
      SELECT TRY_CONVERT(XML, [qsp].[query_plan]) AS [query_plan]
      FROM sys.query_store_plan [qsp]
    ) tp
  ) AS tab (query_plan)
  CROSS APPLY query_plan.nodes('/ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple') AS batch(stmt)
  CROSS APPLY stmt.nodes('.//IndexScan/Object[@Index=sql:variable("@IndexName")]') AS idx(obj)
  OPTION(MAXDOP 1, RECOMPILE);

Vale a pena notar que este é outro ponto em que posso me encontrar muito fundo em uma toca de coelho, dependendo do número de índices que estou revisando e do número de consultas que os usam. Se possível, também considerarei as contagens de execução (do Query Store ou do cache do plano) para não apenas entender o que consulta usa um índice, mas com que frequência essa consulta é executada. É aqui que o ajuste de índice se torna uma arte. Posso coletar uma quantidade absurda de dados… mas não tenho tempo infinito para análise, então preciso fazer um julgamento sobre quantas consultas vou revisar.
Etapa 6:Teste

Na sua forma mais simples, testar um índice significa pegar a consulta problemática e capturar o plano e os dados de desempenho (duração, IO, CPU etc.) e, em seguida, criar o índice, executar novamente a consulta e capturar as mesmas informações. Se o desempenho melhorar, você está pronto para ir!

Raramente é tão simples.

Para começar, geralmente tenho pelo menos duas variações de um índice que quero testar, às vezes mais. Começo com minha linha de base, depois crio todas as variações de índice, limpo o cache do plano e vejo o que o SQL Server escolhe. Em seguida, passo e forço cada índice com uma dica, capturando o plano e as métricas de desempenho para cada execução. Nota:isso pressupõe que eu tenha espaço em disco suficiente para todos os índices… se não, eu os crio um de cada vez e testo. Por fim, comparo os números. Se estou apenas adicionando um novo índice, estou quase terminando. Mas se estou modificando um índice ou mesclando alguns, pode ficar complicado.

Em um mundo ideal, se eu modificar um índice existente, encontro as consultas mais frequentes/importantes que usam o índice atual e obtenho seus planos e métricas de desempenho (isso é fácil com o Query Store). Em seguida, altero o índice, executo todas essas consultas novamente e vejo se obtenho alterações significativas na forma e/ou desempenho do plano.

Se eu mesclar dois índices, faço a mesma coisa, mas com todas as consultas que usam um dos índices e, em seguida, testo novamente com o índice mesclado.

Se estou adicionando/alterando/mesclando vários índices para uma tabela, preciso obter todas as consultas relevantes e seus planos e métricas, alterar os índices, obter todas as informações novamente e comparar. Isso pode ser extremamente demorado, dependendo de quantas consultas diferentes existem. É aqui que é uma forma de arte e você precisa determinar quantas consultas você realmente precisa testar. É uma função da frequência de execução, importância/relevância da consulta e o tempo que tenho disponível/alocado.

Por fim, se eu adicionar um índice a uma tabela e não remover nenhum existente, adicionarei sobrecarga para INSERTs, DELETEs e potencialmente UPDATEs. O teste de desempenho dessa alteração é possível, mas você precisa de um ambiente de teste e da capacidade de executar um teste de carga e capturar métricas pré e pós-alteração relacionadas à duração, E/S e CPU.

São muitos amigos, e é por isso que é irônico que eu inicialmente tenha pensado em afirmar que o ajuste do índice era fácil. Pode nem sempre ser simples, mas é possível. É uma questão de diligência e manter o controle de tudo.
Etapa 7:Implementação

Depois de examinar o(s) novo(s) índice(s) tanto quanto possível, estamos prontos para a produção. Admito que vejo as mudanças de índice como de baixo risco, principalmente as novas. Se for um problema, você pode soltá-lo imediatamente e reverter para o estado original. Com um cenário de modificar/mesclar/descartar, você deseja ter tudo com script, para que possa alterar e recriar índices conforme necessário para redefinir os índices. Eu sempre recomendo desabilitar inicialmente os índices em vez de eliminá-los, pois assim você não precisa se preocupar com a definição - se você precisar adicionar o índice de volta, basta reconstruí-lo.
Resumo

Seu método para adicionar e/ou consolidar índices pode ser diferente! Assim como o ajuste de consultas, não existe um processo perfeito. Para quem é novo no ajuste de índice, esperamos que isso forneça um iniciador de itens para revisão e considerações importantes. É impossível adicionar índices sem adicionar alguma sobrecarga – e novamente é aí que entra a arte:você precisa determinar se o benefício do índice supera o custo das modificações.

O ajuste de índice é um processo perpétuo e iterativo – acho que você nunca terminou, porque o código muda, novas tabelas ou funcionalidades são adicionadas e os dados nas tabelas mudam. Kimberly tem duas postagens (https://www.sqlskills.com/blogs/kimberly/spring-cleaning-your-indexes-part-i/ e https://www.sqlskills.com/blogs/kimberly/spring-cleaning- your-indexes-part-ii/) que falam sobre a limpeza de seus índices — agora é a melhor hora para começar! E, finalmente, sempre que alguém perguntar “quantos índices deve haver para uma tabela?” Eu respondo com algo como “o menor número que você precisa para satisfazer o maior número de consultas possível”. Não existe um número mágico - já vi tabelas com zero índices e já vi tabelas com mais de 100 (tenho certeza que alguns de vocês viram contagens mais altas). Nem zero nem 100 são bons, mas o número “certo” é aquele que você precisa descobrir usando os dados disponíveis e sua experiência.