AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 52129
Accepted
GarethD
GarethD
Asked: 2013-10-25 00:31:33 +0800 CST2013-10-25 00:31:33 +0800 CST 2013-10-25 00:31:33 +0800 CST

O índice na coluna computada persistente precisa de pesquisa de chave para obter colunas na expressão computada

  • 772

Eu tenho uma coluna computada persistente em uma tabela que é simplesmente composta de colunas concatenadas, por exemplo

CREATE TABLE dbo.T 
(   
    ID INT IDENTITY(1, 1) NOT NULL CONSTRAINT PK_T_ID PRIMARY KEY,
    A VARCHAR(20) NOT NULL,
    B VARCHAR(20) NOT NULL,
    C VARCHAR(20) NOT NULL,
    D DATE NULL,
    E VARCHAR(20) NULL,
    Comp AS A + '-' + B + '-' + C PERSISTED NOT NULL 
);

Isso Compnão é exclusivo e D é a data de início válida de cada combinação de A, B, C, portanto, uso a seguinte consulta para obter a data final de cada uma A, B, C(basicamente a próxima data inicial para o mesmo valor de Comp):

SELECT  t1.ID,
        t1.Comp,
        t1.D,
        D2 = (  SELECT  TOP 1 t2.D
                FROM    dbo.T t2
                WHERE   t2.Comp = t1.Comp
                AND     t2.D > t1.D
                ORDER BY t2.D
            )
FROM    dbo.T t1
WHERE   t1.D IS NOT NULL -- DON'T CARE ABOUT INACTIVE RECORDS
ORDER BY t1.Comp;

Em seguida, adicionei um índice à coluna computada para auxiliar nesta consulta (e também em outras):

CREATE NONCLUSTERED INDEX IX_T_Comp_D ON dbo.T (Comp, D) WHERE D IS NOT NULL;

O plano de consulta, no entanto, me surpreendeu. Eu teria pensado que, como tenho uma cláusula where informando isso D IS NOT NULLe estou classificando por Comp, e não fazendo referência a nenhuma coluna fora do índice, que o índice na coluna computada poderia ser usado para digitalizar t1 e t2, mas vi um índice agrupado Varredura.

insira a descrição da imagem aqui

Então forcei o uso desse índice para ver se dava um plano melhor:

SELECT  t1.ID,
        t1.Comp,
        t1.D,
        D2 = (  SELECT  TOP 1 t2.D
                FROM    dbo.T t2
                WHERE   t2.Comp = t1.Comp
                AND     t2.D > t1.D
                ORDER BY t2.D
            )
FROM    dbo.T t1 WITH (INDEX (IX_T_Comp_D))
WHERE   t1.D IS NOT NULL
ORDER BY t1.Comp;

Que deu este plano

insira a descrição da imagem aqui

Isso mostra que uma pesquisa de chave está sendo usada, cujos detalhes são:

insira a descrição da imagem aqui

Agora, de acordo com a documentação do SQL-Server:

Você pode criar um índice em uma coluna computada definida com uma expressão determinística, mas imprecisa, se a coluna estiver marcada como PERSISTED na instrução CREATE TABLE ou ALTER TABLE. Isso significa que o Mecanismo de Banco de Dados armazena os valores calculados na tabela e os atualiza quando quaisquer outras colunas das quais a coluna calculada depende são atualizadas. O Mecanismo de Banco de Dados usa esses valores persistentes ao criar um índice na coluna e quando o índice é referenciado em uma consulta. Essa opção permite criar um índice em uma coluna computada quando o Mecanismo de Banco de Dados não puder provar com precisão se uma função que retorna expressões de coluna computada, particularmente uma função CLR criada no .NET Framework, é determinística e precisa.

Portanto, se, como os documentos dizem "o mecanismo de banco de dados armazena os valores calculados na tabela" , e o valor também está sendo armazenado em meu índice, por que é necessária uma pesquisa de chave para obter A, B e C quando eles não são referenciados em a consulta em tudo? Eu suponho que eles estão sendo usados ​​para calcular o Comp, mas por quê? Além disso, por que a consulta pode usar o índice em t2, mas não em t1?

Consultas e DDL no SQL Fiddle

NB, marquei o SQL Server 2008 porque esta é a versão em que meu principal problema está, mas também recebo o mesmo comportamento em 2012.

sql-server sql-server-2008
  • 3 3 respostas
  • 3354 Views

3 respostas

  • Voted
  1. Best Answer
    Paul White
    2013-10-28T16:39:21+08:002013-10-28T16:39:21+08:00

    Por que uma pesquisa de chave é necessária para obter A, B e C quando eles não são referenciados na consulta? Eu suponho que eles estão sendo usados ​​para calcular o Comp, mas por quê?

    As colunas A, B, and C são referenciadas no plano de consulta - elas são usadas pela busca em T2.

    Além disso, por que a consulta pode usar o índice em t2, mas não em t1?

    O otimizador decidiu que a varredura do índice clusterizado era mais barata do que a varredura do índice não clusterizado filtrado e, em seguida, realizar uma pesquisa para recuperar os valores das colunas A, B e C.

    Explicação

    A verdadeira questão é por que o otimizador sentiu a necessidade de recuperar A, B e C para a busca de índice. Esperamos que ele leia a Compcoluna usando uma varredura de índice não clusterizado e, em seguida, execute uma busca no mesmo índice (alias T2) para localizar o registro Top 1.

    O otimizador de consulta expande as referências de coluna computadas antes do início da otimização, para permitir que ele avalie os custos de vários planos de consulta. Para algumas consultas, expandir a definição de uma coluna computada permite que o otimizador encontre planos mais eficientes.

    Quando o otimizador encontra uma subconsulta correlacionada, ele tenta 'desenrolá-la' para uma forma sobre a qual acha mais fácil raciocinar. Se não conseguir encontrar uma simplificação mais eficaz, ele recorre à reescrita da subconsulta correlacionada como um apply (uma junção correlacionada):

    Aplicar reescrita

    Acontece que esse desdobramento de aplicação coloca a árvore de consulta lógica em um formato que não funciona bem com a normalização do projeto (um estágio posterior que procura corresponder expressões gerais a colunas computadas, entre outras coisas).

    No seu caso, a forma como a consulta é escrita interage com os detalhes internos do otimizador, de modo que a definição da expressão expandida não corresponda à coluna computada e você acaba com uma busca que faz referência a colunas A, B, and Cem vez da coluna computada, Comp. Esta é a causa raiz.

    Gambiarra

    Uma ideia para contornar esse efeito colateral é escrever a consulta como uma aplicação manualmente:

    SELECT
        T1.ID,
        T1.Comp,
        T1.D,
        CA.D2
    FROM dbo.T AS T1
    CROSS APPLY
    (  
        SELECT TOP (1)
            D2 = T2.D
        FROM dbo.T AS T2
        WHERE
            T2.Comp = T1.Comp
            AND T2.D > T1.D
        ORDER BY
            T2.D ASC
    ) AS CA
    WHERE
        T1.D IS NOT NULL -- DON'T CARE ABOUT INACTIVE RECORDS
    ORDER BY
        T1.Comp;
    

    Infelizmente, esta consulta não usará o índice filtrado como esperamos. O teste de desigualdade na coluna Ddentro do apply rejeita NULLs, então o predicado aparentemente redundante WHERE T1.D IS NOT NULLé otimizado.

    Sem esse predicado explícito, a lógica de correspondência de índice filtrado decide que não pode usar o índice filtrado. Há várias maneiras de contornar esse segundo efeito colateral, mas a mais fácil é provavelmente alterar a aplicação cruzada para uma aplicação externa (espelhando a lógica da reescrita que o otimizador executou anteriormente na subconsulta correlacionada):

    SELECT
        T1.ID,
        T1.Comp,
        T1.D,
        CA.D2
    FROM dbo.T AS T1
    OUTER APPLY
    (  
        SELECT TOP (1)
            D2 = T2.D
        FROM dbo.T AS T2
        WHERE
            T2.Comp = T1.Comp
            AND T2.D > T1.D
        ORDER BY
            T2.D ASC
    ) AS CA
    WHERE
        T1.D IS NOT NULL -- DON'T CARE ABOUT INACTIVE RECORDS
    ORDER BY
        T1.Comp;
    

    Agora, o otimizador não precisa usar a própria reescrita de aplicação (portanto, a correspondência de coluna calculada funciona como esperado) e o predicado também não é otimizado, portanto, o índice filtrado pode ser usado para ambas as operações de acesso a dados e a busca usa a Compcoluna em ambos os lados:

    Plano de Aplicação Externo

    This would generally be preferred over adding A, B, and C as INCLUDEd columns in the filtered index, because it addresses the root cause of the problem, and does not require widening the index unnecessarily.

    Persisted computed columns

    As a side note, it is not necessary to mark the computed column as PERSISTED, if you don't mind repeating its definition in a CHECK constraint:

    CREATE TABLE dbo.T 
    (   
        ID integer IDENTITY(1, 1) NOT NULL,
        A varchar(20) NOT NULL,
        B varchar(20) NOT NULL,
        C varchar(20) NOT NULL,
        D date NULL,
        E varchar(20) NULL,
        Comp AS A + '-' + B + '-' + C,
    
        CONSTRAINT CK_T_Comp_NotNull
            CHECK (A + '-' + B + '-' + C IS NOT NULL),
    
        CONSTRAINT PK_T_ID 
            PRIMARY KEY (ID)
    );
    
    CREATE NONCLUSTERED INDEX IX_T_Comp_D
    ON dbo.T (Comp, D) 
    WHERE D IS NOT NULL;
    

    The computed column is only required to be PERSISTED in this case if you want to use a NOT NULL constraint or to reference the Comp column directly (instead of repeating its definition) in a CHECK constraint.

    • 23
  2. wBob
    2013-10-29T05:05:39+08:002013-10-29T05:05:39+08:00

    Although this might be a bit of a co-incidence due to the artificial nature of your test data, being as you mentioned SQL 2012 I tried a rewrite:

    SELECT  ID,
            Comp,
            D,
            D2 = LEAD(D) OVER(PARTITION BY COMP ORDER BY D)
    FROM    dbo.T 
    WHERE   D IS NOT NULL
    ORDER BY Comp;
    

    Isso gerou um bom plano de baixo custo usando seu índice e com leituras significativamente mais baixas do que as outras opções (e os mesmos resultados para seus dados de teste).

    Custos do Plan Explorer para quatro opções: Original;  original com dica;  aplicação externa e chumbo

    Suspeito que seus dados reais sejam mais complicados, portanto, pode haver alguns cenários em que essa consulta se comporte semanticamente diferente da sua, mas mostra que às vezes os novos recursos podem fazer uma diferença real.

    Eu experimentei alguns dados mais variados e encontrei alguns cenários correspondentes e outros não:

    --Example 1: results matched
    TRUNCATE TABLE dbo.t
    
    -- Generate some more interesting test data
    ;WITH cte AS
    (
    SELECT TOP 1000 ROW_NUMBER() OVER ( ORDER BY ( SELECT 1 ) ) rn
    FROM master.sys.columns c1
        CROSS JOIN master.sys.columns c2
        CROSS JOIN master.sys.columns c3
    )
    INSERT T (A, B, C, D)
    SELECT  'A' + CAST( a.rn AS VARCHAR(5) ),
            'B' + CAST( a.rn AS VARCHAR(5) ),
            'C' + CAST( a.rn AS VARCHAR(5) ),
            DATEADD(DAY, a.rn + b.rn, '1 Jan 2013')
    FROM cte a
        CROSS JOIN cte b
    WHERE a.rn % 3 = 0
     AND b.rn % 5 = 0
    ORDER BY 1, 2, 3
    GO
    
    
    -- Original query
    SELECT  t1.ID,
            t1.Comp,
            t1.D,
            D2 = (  SELECT  TOP 1 D
                    FROM    dbo.T t2
                    WHERE   t2.Comp = t1.Comp
                    AND     t2.D > t1.D
                    ORDER BY D
                )
    INTO #tmp1
    FROM    dbo.T t1 
    WHERE   t1.D IS NOT NULL
    ORDER BY t1.Comp;
    GO
    
    SELECT  ID,
            Comp,
            D,
            D2 = LEAD(D) OVER(PARTITION BY COMP ORDER BY D)
    INTO #tmp2
    FROM    dbo.T 
    WHERE   D IS NOT NULL
    ORDER BY Comp;
    GO
    
    
    -- Checks ...
    SELECT * FROM #tmp1
    EXCEPT
    SELECT * FROM #tmp2
    
    SELECT * FROM #tmp2
    EXCEPT
    SELECT * FROM #tmp1
    
    
    Example 2: results did not match
    TRUNCATE TABLE dbo.t
    
    -- Generate some more interesting test data
    ;WITH cte AS
    (
    SELECT TOP 1000 ROW_NUMBER() OVER ( ORDER BY ( SELECT 1 ) ) rn
    FROM master.sys.columns c1
        CROSS JOIN master.sys.columns c2
        CROSS JOIN master.sys.columns c3
    )
    INSERT T (A, B, C, D)
    SELECT  'A' + CAST( a.rn AS VARCHAR(5) ),
            'B' + CAST( a.rn AS VARCHAR(5) ),
            'C' + CAST( a.rn AS VARCHAR(5) ),
            DATEADD(DAY, a.rn, '1 Jan 2013')
    FROM cte a
    
    -- Add some more data
    INSERT dbo.T (A, B, C, D)
    SELECT A, B, C, D 
    FROM dbo.T
    WHERE DAY(D) In ( 3, 7, 9 )
    
    
    INSERT dbo.T (A, B, C, D)
    SELECT A, B, C, DATEADD( day, 1, D )
    FROM dbo.T
    WHERE DAY(D) In ( 12, 13, 17 )
    
    
    SELECT * FROM #tmp1
    EXCEPT
    SELECT * FROM #tmp2
    
    SELECT * FROM #tmp2
    EXCEPT
    SELECT * FROM #tmp1
    
    SELECT * FROM #tmp2
    INTERSECT
    SELECT * FROM #tmp1
    
    
    select * from #tmp1
    where comp = 'A2-B2-C2'
    
    select * from #tmp2
    where comp = 'A2-B2-C2'
    
    • 6
  3. Sandr
    2013-10-28T08:58:53+08:002013-10-28T08:58:53+08:00

    Quando tentei executar as mesmas ações, obtive os outros resultados. Em primeiro lugar, meu plano de execução para tabela sem índices é o seguinte:insira a descrição da imagem aqui

    Como podemos ver no Clustered Index Scan (t2), o predicado é usado para determinar as linhas necessárias a serem retornadas (por causa da condição):

    insira a descrição da imagem aqui

    Quando o índice foi adicionado, não importando se foi definido pelo operador WITH ou não, o plano de execução passou a ser o seguinte:

    insira a descrição da imagem aqui

    Como podemos ver, o Clustered Index Scan foi substituído pelo Index Scan. Como vimos acima, o SQL Server utiliza as colunas de origem da coluna computada para realizar o casamento da consulta aninhada. Durante a varredura de índice clusterizado, todos esses valores podem ser adquiridos ao mesmo tempo (sem necessidade de operações adicionais). Quando o índice foi adicionado, a filtragem das linhas necessárias da tabela (na seleção principal) está sendo executada de acordo com o índice, mas os valores das colunas de origem para a coluna computada compainda precisam ser obtidos (última operação Nested Loop) .

    insira a descrição da imagem aqui

    Por causa disso, a operação Key Lookup é usada - para obter os dados das colunas de origem do calculado.

    PS Parece um bug no SQL Server.

    • -1

relate perguntas

  • Quais são as principais causas de deadlocks e podem ser evitadas?

  • Quanto "Padding" coloco em meus índices?

  • Existe um processo do tipo "práticas recomendadas" para os desenvolvedores seguirem para alterações no banco de dados?

  • Como determinar se um Índice é necessário ou necessário

  • Downgrade do SQL Server 2008 para 2005

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Conceder acesso a todas as tabelas para um usuário

    • 5 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    pedrosanta Listar os privilégios do banco de dados usando o psql 2011-08-04 11:01:21 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve