O plano de execução mostra a operação CONVERT_IMPLICIT cara. Posso corrigir isso com a indexação ou preciso alterar a tabela?

24

Eu tenho uma visão muito importante e muito lenta, que inclui algumas condições realmente feias como esta na cláusula where. Também estou ciente de que as junções são brutas e lentas em varchar(13)vez de campos de identidade inteira, mas gostaria de melhorar a consulta simples abaixo que usa esta exibição:

CREATE VIEW [dbo].[vwReallySlowView]  AS  
AS  
SELECT     
  I.booking_no_v32 AS bkno, 
  I.trans_type_v41 AS trantype, 
  B.Assigned_to_v61 AS Assignbk, 
  B.order_date AS dateo, B.HourBooked AS HBooked,   
  B.MinBooked AS MBooked, B.SecBooked AS SBooked, 
  I.prep_on AS Pon, I.From_locn AS Flocn, 
  I.Trans_to_locn AS TTlocn,   
                      (CASE I.prep_on WHEN 'Y' THEN I.PDate ELSE I.FirstDate END) AS PrDate, I.PTimeH AS PrTimeH, I.PTimeM AS PrTimeM,   
                      (CASE WHEN I.RetnDate < I.FirstDate THEN I.FirstDate ELSE I.RetnDate END) AS RDatev, I.bit_field_v41 AS bitField, I.FirstDate AS FDatev, I.BookDate AS DBooked,   
                      I.TimeBookedH AS TBookH, I.TimeBookedM AS TBookM, I.TimeBookedS AS TBookS, I.del_time_hour AS dth, I.del_time_min AS dtm, I.return_to_locn AS rtlocn,   
                      I.return_time_hour AS rth, I.return_time_min AS rtm, (CASE WHEN I.Trans_type_v41 IN (6, 7) AND (I.Trans_qty < I.QtyCheckedOut)   
                      THEN 0 WHEN I.Trans_type_v41 IN (6, 7) AND (I.Trans_qty >= I.QtyCheckedOut) THEN I.Trans_Qty - I.QtyCheckedOut ELSE I.trans_qty END) AS trqty,   
                      (CASE WHEN I.Trans_type_v41 IN (6, 7) THEN 0 ELSE I.QtyCheckedOut END) AS MyQtycheckedout, (CASE WHEN I.Trans_type_v41 IN (6, 7)   
                      THEN 0 ELSE I.QtyReturned END) AS retqty, I.ID, B.BookingProgressStatus AS bkProg, I.product_code_v42, I.return_to_locn, I.AssignTo, I.AssignType,   
                      I.QtyReserved, B.DeprepOn,  
        (CASE  B.DeprepOn       
        WHEN 1 THEN  B.DeprepDateTime     
        ELSE   I.RetnDate  
           END)  AS DeprepDateTime, I.InRack 
FROM         dbo.tblItemtran AS I 

INNER JOIN  -- booking_no = varchar(13)
         dbo.tblbookings AS B ON B.booking_no = I.booking_no_v32  --  string inner-join

INNER JOIN  -- product_code = varchar(13) 
        dbo.tblInvmas AS M ON I.product_code_v42 = M.product_code  --  string inner-join

WHERE     (I.trans_type_v41 NOT IN (2, 3, 7, 18, 19, 20, 21, 12, 13, 22)) AND (I.trans_type_v41 NOT IN (6, 7)) AND (I.bit_field_v41 & 4 = 0) OR  
                      (I.trans_type_v41 NOT IN (6, 7)) AND (I.bit_field_v41 & 4 = 0) AND (B.BookingProgressStatus = 1) OR  
                      (I.trans_type_v41 IN (6, 7)) AND (I.bit_field_v41 & 4 = 0) AND (I.QtyCheckedOut = 0) OR  
                      (I.trans_type_v41 IN (6, 7)) AND (I.bit_field_v41 & 4 = 0) AND (I.QtyCheckedOut > 0) AND (I.trans_qty - (I.QtyCheckedOut - I.QtyReturned) > 0)  

Essa visualização geralmente é usada assim:

select * from vwReallySlowView
where product_code_v42  = 'LIGHTBULB100W'  -- find "100 watt lightbulb" rows

Quando eu executá-lo eu recebo este item plano de execução custando 20 a 80% do custo total do lote, com predicado CONVERT_IMPLICIT( .... &(4))mostrando que parece ser muito lento em fazer estes bitwise boolean testscomo (I.ibitfield & 4 = 0).

Não sou especialista em MS SQL ou no trabalho do tipo DBA em geral, pois na maioria das vezes sou desenvolvedor de software não-SQL. Mas suspeito que essas combinações bit a bit são uma péssima idéia e que seria melhor ter campos booleanos discretos.

De alguma forma, poderia melhorar esse índice que tenho, para lidar melhor com essa visualização sem alterar o esquema (que já está em produção em milhares de locais) ou devo alterar a tabela subjacente que possui vários valores booleanos compactados em um número inteiro bit_field_v41, para corrigir esse problema ?

Aqui está o meu Índice de Cluster no tblItemtranqual está sendo verificado neste plano de execução:

-- goal:  speed up  select * from vwReallySlowView where productcode  = 'X'
CREATE CLUSTERED INDEX [idxtblItemTranProductCodeAndTransType] ON [dbo].[tblItemtran] 
(
    [product_code_v42] ASC,  -- varchar(13)
    [trans_type_v41] ASC     -- int
)WITH ( PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, 
        IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, 
        ALLOW_PAGE_LOCKS  = ON) 
ON [PRIMARY]

Aqui está o plano de execução, para um dos outros produtos que resulta em um custo de 27% nesse CONVERT_IMPLICITpredicado. update Observe que, neste caso, meu pior nó agora é "combinação de hash" em um inner join, que custa 34%. Acredito que esse é um custo que não posso evitar, a menos que evite fazer junções em strings que atualmente não posso livrar-se de. Ambas as INNER JOINoperações na exibição acima estão nos varchar(13)campos.

Ampliado no canto inferior direito:

insira a descrição da imagem aqui

Todo o plano de execução como .sqlplan está disponível no skydrive. Esta imagem é apenas uma visão geral. Clique aqui para ver a imagem por si só.

insira a descrição da imagem aqui

A atualização postou todo o plano de execução. Parece que não consigo descobrir qual o product_codevalor patologicamente ruim, mas uma maneira de fazer isso é em select count(*) from viewvez de criar um único produto. Mas os produtos que são usados ​​apenas em 5% dos registros da tabela subjacente ou menos parecem mostrar custos muito mais baixos na CONVERT_IMPLICIT operação. Se eu fosse consertar o SQL aqui, acho que pegaria a WHEREcláusula bruta na exibição e calcularia e armazenaria o resultado dessa condição de cláusula where gigante como um campo de bits "IncludeMeInTheView", na tabela subjacente . Presto, problema resolvido, certo?

Warren P
fonte
O .SQLPLAN que publiquei é muito menos patológico do que o caso 432k / 17k na imagem original. Peço desculpas por não conseguir mais encontrar o product_codevalor que usei para gerar esse 87%caso patológico com meus dados. As imagens agora mostram 27%. Mais uma vez, peço desculpas pela confusão devido às minhas edições.
Warren P

Respostas:

49

Você não deve confiar muito nas porcentagens de custo nos planos de execução. Esses sempre são custos estimados , mesmo em planos pós-execução com números 'reais' para itens como contagem de linhas. Os custos estimados são baseados em um modelo que funciona muito bem para a finalidade a que se destina: permitir que o otimizador escolha entre diferentes planos de execução de candidatos para a mesma consulta. As informações de custo são interessantes e um fator a ser considerado, mas raramente deve ser uma métrica principal para o ajuste de consultas. A interpretação das informações do plano de execução requer uma visão mais ampla dos dados apresentados.

Operador de busca de índice clusterizado ItemTran

Pesquisa de índice clusterizado ItemTran

Este operador é realmente duas operações em uma. Primeiro, uma operação de busca de índice localiza todas as linhas que correspondem ao predicado e product_code_v42 = 'M10BOLT', em seguida, cada linha tem o predicado residual bit_field_v41 & 4 = 0aplicado. Há uma conversão implícita de bit_field_v41do tipo base ( tinyintou smallint) para integer.

A conversão ocorre porque o operador AND bit a bit (&) requer que ambos os operandos sejam do mesmo tipo. O tipo implícito do valor constante '4' é inteiro e as regras de precedência do tipo de dados significam que o bit_field_v41valor do campo de prioridade mais baixa é convertido.

O problema (como ele é) é facilmente corrigido escrevendo o predicado como bit_field_v41 & CONVERT(tinyint, 4) = 0- o que significa que o valor constante tem a menor prioridade e é convertido (durante a dobragem constante) em vez do valor da coluna. Se não bit_field_v41houver tinyintnenhuma conversão, ocorrerá. Da mesma forma, CONVERT(smallint, 4)poderia ser usado se bit_field_v41for smallint. Dito isso, a conversão não é um problema de desempenho nesse caso, mas ainda é uma boa prática corresponder tipos e evitar conversões implícitas sempre que possível.

A maior parte do custo estimado dessa busca está no tamanho da tabela base. Enquanto a chave de índice em cluster é razoavelmente estreita, o tamanho de cada linha é grande. Uma definição para a tabela não é fornecida, mas apenas as colunas usadas na exibição somam uma largura de linha significativa. Como o índice em cluster inclui todas as colunas, a distância entre as chaves de índice em cluster é a largura da linha , não a largura das chaves de índice . O uso de sufixos de versão em algumas colunas sugere que a tabela real possui ainda mais colunas para versões anteriores.

Observando as colunas de busca, predicado residual e saída, o desempenho desse operador pode ser verificado isoladamente através da criação de uma consulta equivalente (esse 1 <> 2é um truque para evitar a parametrização automática, a contradição é removida pelo otimizador e não aparece no plano de consulta):

SELECT
    it.booking_no_v32,
    it.QtyCheckedOut,
    it.QtyReturned,
    it.Trans_qty,
    it.trans_type_v41
FROM dbo.tblItemTran AS it
WHERE
    1 <> 2
    AND it.product_code_v42 = 'M10BOLT'
    AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0;

O desempenho desta consulta com um cache de dados frio é interessante, pois a leitura antecipada seria afetada pela fragmentação da tabela (índice clusterizado). A chave de cluster para esta tabela convida à fragmentação; portanto, pode ser importante manter (reorganizar ou reconstruir) esse índice regularmente e usar um apropriado FILLFACTORpara permitir espaço para novas linhas entre as janelas de manutenção de índice.

Eu realizei um teste do efeito da fragmentação na leitura antecipada usando dados de amostra gerados usando o SQL Data Generator . Usando as mesmas contagens de linhas da tabela, como mostrado no plano de consulta da pergunta, um índice em cluster altamente fragmentado resultou em SELECT * FROM view15 segundos depois DBCC DROPCLEANBUFFERS. O mesmo teste nas mesmas condições com um índice de cluster recém-reconstruído na tabela ItemTrans concluído em 3 segundos.

Se os dados da tabela geralmente estão inteiramente no cache, o problema de fragmentação é muito menos importante. Mas, mesmo com baixa fragmentação, as linhas largas da tabela podem significar que o número de leituras lógicas e físicas é muito maior do que o esperado. Você também pode experimentar adicionar e remover o explícito CONVERTpara validar minha expectativa de que o problema implícito de conversão não seja importante aqui, exceto como uma violação das práticas recomendadas.

Mais importante é o número estimado de linhas saindo do operador de busca. A estimativa do tempo de otimização é de 165 linhas, mas 4.226 foram produzidas no tempo de execução. Voltarei a esse ponto mais tarde, mas a principal razão da discrepância é que a seletividade do predicado residual (envolvendo o AND bit a bit) é muito difícil para o otimizador prever - na verdade, ele recorre à adivinhação.

Operador de filtro

Operador de filtro

Estou mostrando o predicado de filtro aqui principalmente para ilustrar como as duas NOT INlistas são combinadas, simplificadas e depois expandidas, e também para fornecer uma referência para a seguinte discussão de correspondência de hash. A consulta de teste da pesquisa pode ser expandida para incorporar seus efeitos e determinar o efeito do operador Filtro no desempenho:

SELECT
    it.booking_no_v32,
    it.trans_type_v41,
    it.Trans_qty,
    it.QtyReturned,
    it.QtyCheckedOut
FROM dbo.tblItemTran AS it
WHERE
    it.product_code_v42 = 'M10BOLT'
    AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
    AND
    (
        (
            it.trans_type_v41 NOT IN (2, 3, 6, 7, 18, 19, 20, 21, 12, 13, 22)
            AND it.trans_type_v41 NOT IN (6, 7)
        )
        OR
        (
            it.trans_type_v41 NOT IN (6, 7)
        )
        OR 
        (
            it.trans_type_v41 IN (6, 7)
            AND it.QtyCheckedOut = 0
        )
        OR 
        (
            it.trans_type_v41 IN (6, 7)
            AND it.QtyCheckedOut > 0
            AND it.trans_qty - (it.QtyCheckedOut - it.QtyReturned) > 0
        )
    );

O operador Compute Scalar no plano define a seguinte expressão (o próprio cálculo é adiado até que o resultado seja requerido por um operador posterior):

[Expr1016] = (trans_qty - (QtyCheckedOut - QtyReturned))

O operador Hash Match

A realização de uma junção nos tipos de dados de caracteres não é a razão do alto custo estimado desse operador. A dica de ferramenta do SSMS mostra apenas uma entrada do Hash Keys Probe, mas os detalhes importantes estão na janela Propriedades do SSMS.

O operador Hash Match cria uma tabela de hash usando os valores da booking_no_v32coluna (Hash Keys Build) da tabela ItemTran e, em seguida, procura por correspondências usando a booking_nocoluna (Hash Keys Probe) da tabela Bookings. A dica de ferramenta do SSMS também normalmente mostra um Residual do probe, mas o texto é muito longo para uma dica de ferramenta e é simplesmente omitido.

Um Sonda Residual é semelhante ao Residual visto após a busca do índice anteriormente; o predicado residual é avaliado em todas as linhas que correspondem a hash para determinar se a linha deve ser passada para o operador pai. A localização de correspondências de hash em uma tabela de hash bem equilibrada é extremamente rápida, mas a aplicação de um predicado residual complexo a cada linha correspondente é bastante lenta em comparação. A dica de ferramenta Hash Match no Plan Explorer mostra os detalhes, incluindo a expressão Residual do probe:

Operador de correspondência de hash

O predicado residual é complexo e inclui a verificação do status do progresso da reserva agora que a coluna está disponível na tabela de reservas. A dica de ferramenta também mostra a mesma discrepância entre as contagens estimadas e reais de linhas vistas anteriormente na busca do índice. Pode parecer estranho que grande parte da filtragem seja realizada duas vezes, mas esse é apenas o otimizador otimista. Ele não espera que as partes do filtro que podem ser empurradas para baixo do plano do resíduo da sonda elimine todas as linhas (as estimativas de contagem de linhas são as mesmas antes e depois do filtro), mas o otimizador sabe que pode estar errado sobre isso. A chance de filtrar linhas antecipadamente (reduzindo o custo da junção de hash) vale o pequeno custo do filtro extra. O filtro inteiro não pode ser pressionado para baixo porque inclui um teste em uma coluna da tabela de reservas, mas a maioria pode ser.

A subestimação da contagem de linhas é um problema para o operador Hash Match porque a quantidade de memória reservada para a tabela de hash é baseada no número estimado de linhas. Onde a memória é muito pequena para o tamanho da tabela de hash necessária no tempo de execução (devido ao maior número de linhas), a tabela de hash se derrama recursivamente para o armazenamento físico do tempdb , geralmente resultando em um desempenho muito ruim. Na pior das hipóteses, o mecanismo de execução para de recursivamente derramar baldes de hash e recorre a um processo muito lento.algoritmo de resgate. O derramamento de hash (recursivo ou resgate) é a causa mais provável dos problemas de desempenho descritos na pergunta (não colunas de junção do tipo caractere ou conversões implícitas). A causa raiz seria o servidor reservando pouca memória para a consulta com base na estimativa incorreta da contagem de linhas (cardinalidade).

Infelizmente, antes do SQL Server 2012, não há indicação no plano de execução de que uma operação de hash excedeu sua alocação de memória (que não pode crescer dinamicamente depois de ser reservada antes do início da execução, mesmo que o servidor tenha grandes quantidades de memória livre) e precisou se derramar para tempdb. É possível monitorar a classe de eventos de aviso de hash usando o Profiler, mas pode ser difícil correlacionar os avisos com uma consulta específica.

Corrigindo os problemas

Os três problemas são a fragmentação, a sonda complexa residual no operador de combinação de hash e a estimativa de cardinalidade incorreta resultante da tentativa de adivinhação na busca do índice.

Solução recomendada

Verifique a fragmentação e corrija-a, se necessário, agendando a manutenção para garantir que o índice permaneça organizado de maneira aceitável. A maneira usual de corrigir a estimativa de cardinalidade é fornecer estatísticas. Nesse caso, o otimizador precisa de estatísticas para a combinação ( product_code_v42, bitfield_v41 & 4 = 0). Como não podemos criar estatísticas diretamente em uma expressão, devemos primeiro criar uma coluna computada para a expressão do campo de bits e, em seguida, criar as estatísticas manuais de várias colunas:

ALTER TABLE dbo.tblItemTran
ADD Bit3 AS bit_field_v41 & CONVERT(tinyint, 4);

CREATE STATISTICS [stats dbo.ItemTran (product_code_v42, Bit3)]
ON dbo.tblItemTran (product_code_v42, Bit3);

A definição de texto da coluna calculada deve corresponder exatamente ao texto na definição de visualização para que as estatísticas sejam usadas, portanto, a correção da visualização para eliminar a conversão implícita deve ser feita ao mesmo tempo e deve-se tomar cuidado para garantir uma correspondência textual.

As estatísticas de várias colunas devem resultar em estimativas muito melhores, reduzindo bastante a chance de o operador de combinação de hash usar derramamento recursivo ou o algoritmo de resgate. Adicionar a coluna computada (que é uma operação apenas de metadados e não ocupa espaço na tabela, pois não está marcada PERSISTED) e as estatísticas de várias colunas são meu melhor palpite para uma primeira solução.

Ao resolver problemas de desempenho da consulta, é importante medir coisas como tempo decorrido, uso da CPU, leituras lógicas, leituras físicas, tipos de espera e durações ... e assim por diante. Também pode ser útil executar partes da consulta separadamente para validar as causas suspeitas, como mostrado acima.

Em alguns ambientes, onde uma visualização atualizada dos dados não é importante, pode ser útil executar um processo em segundo plano que materialize toda a visualização em uma tabela de instantâneos de vez em quando. Esta tabela é apenas uma tabela base normal e pode ser indexada para consultas de leitura sem se preocupar com o desempenho da atualização.

Exibir indexação

Não fique tentado a indexar diretamente a exibição original. O desempenho da leitura será incrivelmente rápido (uma única busca em um índice de exibição), mas (nesse caso) todos os problemas de desempenho nos planos de consulta existentes serão transferidos para consultas que modificam qualquer uma das colunas da tabela mencionada na exibição. As consultas que alteram as linhas da tabela base serão muito afetadas.

Solução avançada com uma exibição indexada parcial

Existe uma solução de exibição indexada parcial para essa consulta específica que corrige as estimativas de cardinalidade e remove o filtro e a sonda residual, mas ela é baseada em algumas suposições sobre os dados (principalmente meu palpite no esquema) e requer implementação especializada, particularmente no que se refere a índices para suportar os planos de manutenção de exibição indexada. Eu compartilho o código abaixo por interesse, não proponho que você o implemente sem análises e testes muito cuidadosos .

-- Indexed view to optimize the main view
CREATE VIEW dbo.V1
WITH SCHEMABINDING
AS
SELECT
    it.ID,
    it.product_code_v42,
    it.trans_type_v41,
    it.booking_no_v32,
    it.Trans_qty,
    it.QtyReturned,
    it.QtyCheckedOut,
    it.QtyReserved,
    it.bit_field_v41,
    it.prep_on,
    it.From_locn,
    it.Trans_to_locn,
    it.PDate,
    it.FirstDate,
    it.PTimeH,
    it.PTimeM,
    it.RetnDate,
    it.BookDate,
    it.TimeBookedH,
    it.TimeBookedM,
    it.TimeBookedS,
    it.del_time_hour,
    it.del_time_min,
    it.return_to_locn,
    it.return_time_hour,
    it.return_time_min,
    it.AssignTo,
    it.AssignType,
    it.InRack
FROM dbo.tblItemTran AS it
JOIN dbo.tblBookings AS tb ON
    tb.booking_no = it.booking_no_v32
WHERE
    (
        it.trans_type_v41 NOT IN (2, 3, 7, 18, 19, 20, 21, 12, 13, 22)
        AND it.trans_type_v41 NOT IN (6, 7)
        AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
    )
    OR
    (
        it.trans_type_v41 NOT IN (6, 7)
        AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
        AND tb.BookingProgressStatus = 1
    )
    OR 
    (
        it.trans_type_v41 IN (6, 7)
        AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
        AND it.QtyCheckedOut = 0
    )
    OR 
    (
        it.trans_type_v41 IN (6, 7)
        AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
        AND it.QtyCheckedOut > 0
        AND it.trans_qty - (it.QtyCheckedOut - it.QtyReturned) > 0
    );
GO
CREATE UNIQUE CLUSTERED INDEX cuq ON dbo.V1 (product_code_v42, ID);
GO

A visualização existente foi ajustada para usar a visualização indexada acima:

CREATE VIEW [dbo].[vwReallySlowView2]
AS
SELECT
    I.booking_no_v32 AS bkno,
    I.trans_type_v41 AS trantype,
    B.Assigned_to_v61 AS Assignbk,
    B.order_date AS dateo,
    B.HourBooked AS HBooked,
    B.MinBooked AS MBooked,
    B.SecBooked AS SBooked,
    I.prep_on AS Pon,
    I.From_locn AS Flocn,
    I.Trans_to_locn AS TTlocn,
    CASE I.prep_on 
        WHEN 'Y' THEN I.PDate
        ELSE I.FirstDate
    END AS PrDate,
    I.PTimeH AS PrTimeH,
    I.PTimeM AS PrTimeM,
    CASE
        WHEN I.RetnDate < I.FirstDate 
        THEN I.FirstDate 
        ELSE I.RetnDate
    END AS RDatev,
    I.bit_field_v41 AS bitField,
    I.FirstDate AS FDatev,
    I.BookDate AS DBooked,
    I.TimeBookedH AS TBookH,
    I.TimeBookedM AS TBookM,
    I.TimeBookedS AS TBookS,
    I.del_time_hour AS dth,
    I.del_time_min AS dtm,
    I.return_to_locn AS rtlocn,
    I.return_time_hour AS rth,
    I.return_time_min AS rtm,
    CASE
        WHEN
            I.Trans_type_v41 IN (6, 7) 
            AND I.Trans_qty < I.QtyCheckedOut
            THEN 0 
        WHEN
            I.Trans_type_v41 IN (6, 7)
            AND I.Trans_qty >= I.QtyCheckedOut
            THEN I.Trans_Qty - I.QtyCheckedOut
        ELSE
            I.trans_qty
    END AS trqty,
    CASE
        WHEN I.Trans_type_v41 IN (6, 7)
        THEN 0
        ELSE I.QtyCheckedOut
    END AS MyQtycheckedout,
    CASE
        WHEN I.Trans_type_v41 IN (6, 7)
        THEN 0
        ELSE I.QtyReturned
    END AS retqty,
    I.ID,
    B.BookingProgressStatus AS bkProg,
    I.product_code_v42,
    I.return_to_locn,
    I.AssignTo,
    I.AssignType,
    I.QtyReserved,
    B.DeprepOn,
    CASE B.DeprepOn
        WHEN 1 THEN B.DeprepDateTime
        ELSE I.RetnDate
    END AS DeprepDateTime,
    I.InRack
FROM dbo.V1 AS I WITH (NOEXPAND)
JOIN dbo.tblbookings AS B ON
    B.booking_no = I.booking_no_v32
JOIN dbo.tblInvmas AS M ON
    I.product_code_v42 = M.product_code;

Exemplo de consulta e plano de execução:

SELECT
    vrsv.*
FROM dbo.vwReallySlowView2 AS vrsv
WHERE vrsv.product_code_v42 = 'M10BOLT';

Novo plano de execução

No novo plano, a correspondência de hash não tem predicado residual , não há filtro complexo , predicado residual na busca de exibição indexada e as estimativas de cardinalidade estão exatamente corretas.

Como um exemplo de como os planos de inserção / atualização / exclusão seriam afetados, este é o plano para uma inserção na tabela ItemTrans:

Inserir plano

A seção destacada é nova e necessária para a manutenção da exibição indexada. O spool de tabela substitui as linhas de tabela base inseridas para manutenção de exibição indexada. Cada linha é associada à tabela de reservas usando uma busca de índice em cluster e, em seguida, um filtro aplica os WHEREpredicados de cláusula complexa para ver se a linha precisa ser adicionada à visualização. Nesse caso, uma inserção é executada no índice de cluster da exibição.

O mesmo SELECT * FROM viewteste realizado anteriormente foi concluído em 150ms com a exibição indexada no lugar.

Final: notei que seu servidor 2008 R2 ainda está na RTM. Ele não solucionará seus problemas de desempenho, mas o Service Pack 2 para 2008 R2 está disponível desde julho de 2012 e há muitos bons motivos para manter o mais atualizado possível com os service packs.

Paul White diz que a GoFundMonica
fonte