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

Como os índices filtrados podem ser um recurso mais poderoso


Não me entenda mal; Eu amo índices filtrados. Eles criam oportunidades para um uso muito mais eficiente de E/S e, finalmente, nos permitem implementar restrições exclusivas adequadas em conformidade com ANSI (onde mais de um NULL é permitido). No entanto, eles estão longe de ser perfeitos. Eu queria apontar algumas áreas onde os índices filtrados podem ser melhorados e torná-los muito mais úteis e práticos para uma grande parte das cargas de trabalho existentes.

Primeiro, as boas notícias


Índices filtrados podem fazer um trabalho muito rápido de consultas anteriormente caras e fazem isso usando menos espaço (e, portanto, E/S reduzida, mesmo quando varridas).

Um exemplo rápido usando Sales.SalesOrderDetailEnlarged (criado usando este script por Jonathan Kehayias (@SQLPoolBoy)). Esta tabela tem 4,8 milhões de linhas, com 587 MB de dados e 363 MB de índices. Há apenas uma coluna anulável, CarrierTrackingNumber , então vamos brincar com esse. Como está, a tabela atualmente tem cerca de metade desses valores (2,4MM) como NULL. Vou reduzir isso para cerca de 240K para simular um cenário em que uma pequena porcentagem das linhas na tabela é realmente qualificada para um índice, a fim de destacar melhor os benefícios de um índice filtrado. A consulta a seguir afeta 2,17 milhões de linhas, deixando 241.507 linhas com um valor NULL para CarrierTrackingNumber :
UPDATE Sales.SalesOrderDetailEnlarged 
    SET CarrierTrackingNumber = 'x'
      WHERE CarrierTrackingNumber IS NULL
      AND SalesOrderID % 10 <> 3;

Agora, digamos que há um requisito de negócios em que queremos revisar constantemente os pedidos que têm produtos que ainda não receberam um número de rastreamento (pense nos pedidos que são divididos e enviados separadamente). Na tabela atual, executaríamos essas consultas (e adicionei os comandos DBCC para garantir o cache frio em todos os casos):
DBCC DROPCLEANBUFFERS;
DBCC FREEPROCCACHE;
 
SELECT COUNT(*)
  FROM Sales.SalesOrderDetailEnlarged 
  WHERE CarrierTrackingNumber IS NULL;
 
SELECT ProductID, SalesOrderID
  FROM Sales.SalesOrderDetailEnlarged
  WHERE CarrierTrackingNumber IS NULL;

Que exigem varreduras de índice clusterizado e geram as seguintes métricas de tempo de execução (conforme capturadas com o SQL Sentry Plan Explorer):



Nos tempos "antigos" (ou seja, desde o SQL Server 2005), teríamos criado esse índice (e de fato, mesmo no SQL Server 2012, esse é o índice que o SQL Server recomenda):
CREATE INDEX IX_NotVeryHelpful
ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber])
INCLUDE ([SalesOrderID],[ProductID]);

Com esse índice em vigor e executando as consultas acima novamente, aqui estão as métricas, com ambas as consultas usando uma busca de índice como você poderia esperar:



E, em seguida, descartando esse índice e criando um ligeiramente diferente, simplesmente adicionando um WHERE cláusula:
CREATE INDEX IX_Filtered_CTNisNULL
ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber])
INCLUDE ([SalesOrderID],[ProductID])
WHERE CarrierTrackingNumber IS NULL;

Obtemos esses resultados e ambas as consultas usam o índice filtrado para suas buscas:



Aqui está o espaço adicional exigido por cada índice, em comparação com a redução no tempo de execução e E/S das consultas acima:
Índice Espaço do índice Adicionado espaço Duração
Nenhum índice dedicado 363 MB 15.700 ms ~164.000
Índice não filtrado 530 MB 167 MB (+46%) 169ms 1.084
Índice filtrado 367 MB 4 MB (+1%) 170ms 1.084


Assim, como você pode ver, o índice filtrado oferece melhorias de desempenho quase idênticas ao índice não filtrado (já que ambos podem obter seus dados usando o mesmo número de leituras), mas com um armazenamento muito menor custo, uma vez que o índice filtrado só precisa armazenar e manter as linhas que correspondem ao predicado do filtro.

Agora, vamos colocar a tabela de volta ao seu estado original:
UPDATE Sales.SalesOrderDetailEnlarged
  SET CarrierTrackingNumber = NULL
  WHERE CarrierTrackingNumber = 'x';
 
DROP INDEX IX_NotVeryHelpful ON Sales.SalesOrderDetailEnlarged;
DROP INDEX IX_Filtered_CTNisNULL ON Sales.SalesOrderDetailEnlarged;

Tim Chapman (@chapmandew) e Michelle Ufford (@sqlfool) fizeram um trabalho fantástico descrevendo os benefícios de desempenho dos índices filtrados de suas próprias maneiras, e você também deve conferir suas postagens:
  • Michelle Ufford:índices filtrados:o que você precisa saber
  • Tim Chapman:as alegrias dos índices filtrados

Além disso, restrições exclusivas compatíveis com ANSI (mais ou menos)


Pensei em também mencionar brevemente as restrições exclusivas compatíveis com ANSI. No SQL Server 2005, criaríamos uma restrição exclusiva como esta:
CREATE TABLE dbo.Personnel
(
  EmployeeID INT PRIMARY KEY,
  SSN CHAR(9) NULL,
  -- ... other columns ...
  CONSTRAINT UQ_SSN UNIQUE(SSN)
);

(Também podemos criar um índice exclusivo não clusterizado em vez de uma restrição; a implementação subjacente é essencialmente a mesma.)

Agora, isso não é problema se os SSNs forem conhecidos no momento da entrada:
INSERT dbo.Personnel(EmployeeID, SSN)
VALUES(1,'111111111'),(2,'111111112');

Também não há problema se tivermos o SSN ocasional que não é conhecido no momento da entrada (pense em um solicitante de visto ou talvez até mesmo em um trabalhador estrangeiro que não tenha um SSN e nunca terá):
INSERT dbo.Personnel(EmployeeID, SSN)
VALUES(3,NULL);

Até agora tudo bem. Mas o que acontece quando temos um segundo funcionário com um SSN desconhecido?
INSERT dbo.Personnel(EmployeeID, SSN)
VALUES(4,NULL);

Resultado:
Msg 2627, Level 14, State 1, Line 1
Violação da restrição UNIQUE KEY 'UQ_SSN'. Não é possível inserir a chave duplicada no objeto 'dbo.Personnel'. O valor da chave duplicada é ().
A instrução foi encerrada.
Portanto, a qualquer momento, apenas um valor NULL pode existir nesta coluna. Ao contrário da maioria dos cenários, este é um caso em que o SQL Server trata dois valores NULL como iguais (em vez de determinar que a igualdade é simplesmente desconhecida e, por sua vez, falsa). As pessoas reclamam dessa inconsistência há anos.

Se isso for um requisito, agora podemos contornar isso usando índices filtrados:
ALTER TABLE dbo.Personnel DROP CONSTRAINT UQ_SSN;
GO
 
CREATE UNIQUE INDEX UQ_SSN ON dbo.Personnel(SSN)
  WHERE SSN IS NOT NULL;

Agora, nossa 4ª inserção funciona bem, pois a exclusividade é aplicada apenas nos valores não NULL. Isso é uma espécie de trapaça, mas atende aos requisitos básicos que o padrão ANSI pretendia (mesmo que o SQL Server não nos permita usar ALTER TABLE ... ADD CONSTRAINT sintaxe para criar uma restrição exclusiva filtrada).

Mas, segure o telefone


Esses são ótimos exemplos do que podemos fazer com índices filtrados, mas há muitas coisas que ainda não podemos fazer e várias limitações e problemas que surgem como resultado.

Atualizações de estatísticas


Esta é uma das limitações mais importantes do IMHO. Os índices filtrados não se beneficiam da atualização automática de estatísticas com base em uma alteração percentual do subconjunto da tabela que é identificado pelo predicado do filtro; ele é baseado (como todos os índices não filtrados) no churn de toda a tabela. Isso significa que, dependendo de qual porcentagem da tabela está no índice filtrado, o número de linhas no índice pode quadruplicar ou reduzir pela metade e as estatísticas não serão atualizadas, a menos que você faça isso manualmente. Kimberly Tripp deu ótimas informações sobre isso (e Gail Shaw cita um exemplo em que foram necessárias 257.000 atualizações antes que as estatísticas fossem atualizadas para um índice filtrado que continha apenas 10.000 linhas):

http://www.sqlskills.com/blogs/kimberly/filtered-indexes-and-filtered-stats-might-become-seriously-out-of-date/
http://www.sqlskills.com/ blogs/kimberly/category/filtered-indexes/

Além disso, o colega de Kimberly, Joe Sack (@JosephSack), apresentou um item do Connect que sugere a correção desse comportamento para índices filtrados e estatísticas filtradas.

Limitações da expressão de filtro


Existem várias construções que você não pode usar em um predicado de filtro, como NOT IN , OR e predicados dinâmicos/não determinísticos como WHERE col >= DATEADD(DAY, -1, GETDATE()) . Além disso, o otimizador pode não reconhecer um índice filtrado se o predicado não corresponder exatamente a WHERE cláusula na definição do índice. Aqui estão alguns itens do Connect que tentam obter algum suporte para uma melhor cobertura aqui:
O índice filtrado não permite filtros em disjunções (fechado:por design)
Falha na criação do índice filtrado com cláusula NOT IN (fechado:por design)
Suporte para cláusula WHERE mais complexa em índices filtrados (ativo)

Outros usos potenciais atualmente não são possíveis


Atualmente, não podemos criar um índice filtrado em uma coluna computada persistente, mesmo que seja determinística. Não podemos apontar uma chave estrangeira para um índice filtrado exclusivo; se quisermos que um índice dê suporte à chave estrangeira além das consultas suportadas pelo índice filtrado, devemos criar um segundo índice redundante e não filtrado. E aqui estão algumas outras limitações semelhantes que foram negligenciadas ou ainda não consideradas:
Deve ser possível criar um índice filtrado em uma coluna computada persistente determinística (ativo)
Permitir que o índice exclusivo filtrado seja uma chave candidata para uma chave estrangeira (ativo)
capacidade de criar índices de filtro em visualizações indexadas (fechado:não corrigirá)
Erro de particionamento 1908 – Melhorar o particionamento (fechado:não corrigirá)
CRIAR ÍNDICE DE COLUNAS "FILTRADO" (ativo)

Problemas com MERGE


E MERGE faz mais uma aparição na minha lista de "cuidado":
MERGE avalia o índice filtrado por linha, não pós-operação, o que causa a violação do índice filtrado (fechado:não corrigirá)
MERGE falha ao atualizar com índice filtrado em vigor (fechado:fixo)
Erro de instrução MERGE quando INSERT/DELETE usado e índice filtrado (ativo)
MERGE informa incorretamente violações de chave exclusivas (ativo)


Embora um desses bugs (aparentemente relacionados) diga que foi corrigido no SQL Server 2012, talvez seja necessário entrar em contato com o PSS se estiver enfrentando alguma variação desse problema, principalmente em versões anteriores (ou parar de usar MERGE , como sugeri anteriormente).

Limitações de ferramentas/DMV/integradas


Existem muitos DMVs, comandos DBCC, procedimentos do sistema e ferramentas de cliente nos quais começamos a confiar ao longo do tempo. No entanto, nem todas essas coisas são atualizadas para aproveitar os novos recursos; índices filtrados não são exceção. Os seguintes itens do Connect apontam alguns problemas que podem atrapalhar você se você espera que eles funcionem com índices filtrados:
Não há como criar um índice filtrado do SSMS ao projetar uma nova tabela (fechado:não corrigirá)
A expressão de filtro de um índice filtrado é perdida quando uma tabela é modificada pelo Table Designer (fechado:não corrigirá)
O designer de tabela não cria scripts da cláusula WHERE em índices filtrados (ativo)
O designer de tabela do SSMS não preserva a expressão do filtro de índice na reconstrução da tabela (fechado:não corrigirá)
Saída incorreta DBCC PAGE com índices filtrados (ativo)
Sugestões de índice filtrado do SQL 2008 de visualizações de DM e DTA (fechado:não corrigirá)
Aprimoramentos nos DMVs de índices ausentes para índices filtrados (fechado:não corrigirá)
Erro de sintaxe ao replicar índices filtrados compactados (fechado:não corrigirá)
Agente:as tarefas usam opções não padrão ao executar um script T-SQL (fechado:não corrigirá)
Exibir dependências falha com o erro Transact-SQL 515 (ativo)
Exibir dependências falha em determinados objetos (fechado:não corrigirá)
As diferenças de opções de índice não são detectadas na comparação de esquema para dois bancos de dados (fechado:externo)
Sugerir a exposição da condição de filtro de índice em todas as visualizações de informações de índice (fechado:não corrigirá)
os resultados de sp_helpIndex devem incluir a expressão Filtro de Índices de Filtro (ativo)
Sobrecarregar sp_help, sp_columns, sp_helpindex para recursos de 2008 (fechado:não corrigirá)


Para os últimos três, não prenda a respiração – é muito improvável que a Microsoft invista tempo nos procedimentos sp_, DMVs, visualizações INFORMATION_SCHEMA, etc. Em vez disso, consulte as reescritas sp_helpindex de Kimberly Tripp, que incluem informações sobre índices filtrados com outros novos recursos que a Microsoft deixou para trás.

Limitações do Otimizador


Há vários itens do Connect que descrevem casos em que os índices filtrados *podem* ser usados ​​pelo otimizador, mas são ignorados. Em alguns casos, estes não são considerados "bugs", mas sim "lacunas na funcionalidade"…
SQL não usa índice filtrado em uma consulta simples (fechado:por design)
O plano de execução do índice filtrado não está otimizado (fechado:não corrigirá)
Índice filtrado não usado e pesquisa de chave sem saída (fechado:não corrigirá)
O uso do índice filtrado na coluna BIT depende da expressão SQL exata usada na cláusula WHERE (ativo)
A consulta do servidor vinculado não é otimizada corretamente quando existe um índice exclusivo filtrado (fechado:não corrigirá)
Row_Number() fornece resultados imprevisíveis em servidores vinculados onde índices filtrados são usados (fechado:sem reprodução)
Índice filtrado óbvio não usado pelo QP (fechado:por design)
Reconhecer índices filtrados exclusivos como exclusivos (ativo)


Paul White (@SQL_Kiwi) postou recentemente aqui no SQLPerformance.com um post que detalha algumas das limitações do otimizador acima.

E Tim Chapman escreveu um ótimo post descrevendo algumas outras limitações de índices filtrados – como a incapacidade de corresponder o predicado a uma variável local (corrigida em 2008 R2 SP1) e a incapacidade de especificar um índice filtrado em uma dica de índice.

Conclusão


Índices filtrados têm um grande potencial e eu tinha grandes esperanças para eles quando foram introduzidos pela primeira vez no SQL Server 2008. No entanto, a maioria das limitações que acompanham sua primeira versão ainda existem hoje, uma e meia (ou duas, dependendo do seu perspectiva) grandes lançamentos posteriores. O acima parece uma lista bastante extensa de itens que precisam ser abordados, mas eu não queria que isso acontecesse dessa maneira. Só quero que as pessoas estejam cientes do grande número de possíveis problemas que podem precisar considerar ao aproveitar os índices filtrados.