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 / 296111
Accepted
Daniel Hutmacher
Daniel Hutmacher
Asked: 2021-07-25 06:46:57 +0800 CST2021-07-25 06:46:57 +0800 CST 2021-07-25 06:46:57 +0800 CST

Como posso obter o pushdown de predicado na minha visão

  • 772

Eu tenho uma tabela de relatórios (cerca de 1 bilhão de linhas) e uma pequena tabela de dimensões:

CREATE TABLE dbo.Sales_unpartitioned (
    BusinessUnit    int NOT NULL,
    [Date]          date NOT NULL,
    SKU             varchar(8) NOT NULL,
    Quantity        numeric(10, 2) NOT NULL,
    Amount          numeric(10, 2) NOT NULL,
    CONSTRAINT PK_Sales_unpartitioned PRIMARY KEY CLUSTERED (BusinessUnit, [Date], SKU)
);

--- Demo data:
INSERT INTO dbo.Sales_unpartitioned
SELECT severity AS BusinessUnit,
       DATEADD(day, message_id, '2000-01-01') AS [Date],
       LEFT([text], 3) AS SKU,
       1000.*RAND(CHECKSUM(NEWID())) AS Quantity,
       10000.*RAND(CHECKSUM(NEWID())) AS Amount
FROM sys.messages
WHERE [language_id]=1033;

--- Artificially inflate statistics of demo data:
UPDATE STATISTICS dbo.Sales_unpartitioned WITH ROWCOUNT=1000000000;

--- Dimension table:
CREATE TABLE dbo.BusinessUnits (
    BusinessUnit    int NOT NULL,
    SalesManager    nvarchar(250) NULL,
    PRIMARY KEY CLUSTERED (BusinessUnit)
);

INSERT INTO dbo.BusinessUnits (BusinessUnit)
SELECT DISTINCT BusinessUnit FROM dbo.Sales;

... ao qual adicionei uma exibição de relatórios usada por um aplicativo para relatórios no estilo OLTP.

CREATE OR ALTER VIEW dbo.SalesReport_unpartitioned
AS

SELECT bu.BusinessUnit,
       s.[Date],
       s.SKU,
       s.Quantity,
       s.Amount
FROM dbo.BusinessUnits AS bu
CROSS APPLY (
    --- Regular sales
    SELECT t.BusinessUnit, t.[Date], t.SKU, t.Quantity, t.Amount
    FROM dbo.Sales_unpartitioned AS t
    WHERE t.BusinessUnit=bu.BusinessUnit
      AND t.SKU LIKE 'T%'

    UNION ALL

    --- This is a special reporting entry. We only
    --- want to see today's row. In case of duplicates,
    --- get the row with the first "SKU".
    SELECT TOP (1) s.BusinessUnit, s.[Date], s.SKU, s.Quantity, s.Amount
    FROM dbo.Sales_unpartitioned AS s
    WHERE s.BusinessUnit=bu.BusinessUnit
      AND s.[Date]=CAST(SYSDATETIME() AS date)
      AND s.SKU LIKE 'S%'
    ORDER BY s.BusinessUnit, s.[Date], s.SKU
) AS s

A ideia é que o aplicativo do usuário consulte essa exibição com uma consulta SELECT que filtra um intervalo de datas e uma ou mais Unidades de Negócios. Para isso, escolhi um CROSS APPLYpadrão, para que a consulta possa fazer um "loop" em cada Unidade de Negócios, buscar um intervalo de Datas e aplicar um filtro residual no SKU.

Exemplo de consulta de aplicativo:

DECLARE @from date='2021-01-01', @to date='2021-12-31';

SELECT *
FROM dbo.SalesReport_unpartitioned
WHERE BusinessUnit=16
  AND [Date] BETWEEN @from AND @to
ORDER BY BusinessUnit, [Date], SKU;

Eu esperaria um plano de consulta parecido com este: Plano desejadoPlano de consulta desejado com pushdown de predicado e sem operador de filtro

No entanto, o plano fica assim: Plano realO plano de consulta real busca apenas no BusinessUnit com predicado residual no SKU, adiciona Filtro ao final do plano

Eu esperava que o SQL Server fizesse um "empilhamento de predicado" na coluna Data, permitindo que o Clustered Index Seek procurasse uma única unidade de negócios e um intervalo de datas e, em seguida, aplicasse um predicado residual no SKU. Isso funciona no Seek na ramificação "s" (aquele com TOP) - provavelmente porque tem um predicado Date codificado na consulta - mas não na ramificação "t".

No entanto, na ramificação "t" o SQL Server busca apenas a BusinessUnit específica com um predicado residual no SKU, recuperando efetivamente todas as datas . Somente no final do plano é aplicado um operador Filtro que filtra na coluna Data.

Em uma tabela grande, isso tem uma penalidade de desempenho muito significativa - você pode acabar lendo 20 anos de dados do disco quando tudo o que você procura é uma semana.

Coisas que eu tentei

Soluções alternativas:

  • Converter a exibição em uma função com valor de tabela embutida com os parâmetros @fromDate e @toDate que filtram as consultas "s" e "t" habilitará uma Busca em (BusinessUnit, Date) conforme desejado, mas requer a reescrita do código do aplicativo.
  • Mover a UNION ALLsaída de CROSS APPLY(de CROSS APPLY (UNION)para CROSS APPLY() UNION CROSS APPLY()) habilitará o pushdown de predicado. Faz mais uma busca na mesa da BusinessUnit, o que é perfeitamente aceitável.

Corrige o Seek, mas altera os resultados:

  • Surpreendentemente, remover o TOP (1)and ORDER BYpara a consulta "s" faz o empilhamento de predicado funcionar em "t", mas pode retornar muitas linhas de "s".
  • A eliminação UNION ALLremovendo a consulta "s" ou "t" habilitará o empilhamento de predicado, mas gerará resultados incorretos.

Sem alteração ou inviável:

  • Substituir TOP (1)por um ROW_NUMBER()padrão não altera a Busca.
  • Alterar o CROSS APPLYpara uma correção forçada INNER LOOP JOINdo Seek em "t", mas na verdade altera "s" para um Scan, o que é ainda pior.
  • Adicionar o sinalizador de rastreamento 8780 para permitir que o otimizador trabalhe em um plano por mais tempo não altera nada. O plano já está otimizado FULL sem rescisão antecipada.

Um tópico comum parece ser que alterar/simplificar a consulta "s" (remover TOP, ORDER BY) corrige o problema na consulta "t", o que parece contra-intuitivo para mim.

O que estou olhando

Estou tentando entender se isso é uma falha do otimizador, se é o resultado de um mecanismo de otimização/custo deliberado, ou se eu simplesmente ignorei algo.

optimization query-performance
  • 2 2 respostas
  • 799 Views

2 respostas

  • Voted
  1. Best Answer
    Paul White
    2021-07-26T08:22:36+08:002021-07-26T08:22:36+08:00

    Estou tentando entender se isso é uma falha do otimizador, se é o resultado de um mecanismo de otimização/custo deliberado, ou se eu simplesmente ignorei algo.

    É um pouco de tudo isso.

    Há muita coisa acontecendo na consulta apresentada - demais na verdade - então, para evitar escrever meio livro sobre isso, vou resumir ao elemento principal que está fazendo com que você não consiga o plano que procura:

    O otimizador não empurra predicados para o lado interno de uma aplicação.

    A regra que opera em seleções relacionais (filtros, predicados) acima de um apply chama-se, naturalmente, SELonApply. Ela realiza a seguinte substituição lógica:

    Sel (A Aplicar B) -> Sel (Sel A Aplicar B)

    Ele toma parte(s) de uma seleção potencialmente complexa envolvendo A e B, e empurra as partes que puder para a mesa de acionamento A. Nenhuma parte da seleção é empurrada para B. A(s) parte(s) da seleção que não pode(m) ser empurrado para baixo ficam para trás.


    Isso pode soar como um descuido chocante e contrário à experiência. Isso porque não é a história completa.

    O otimizador tenta converter uma aplicação para a junção equivalente no início do processo de compilação (durante a simplificação, antes do plano trivial e da otimização baseada em custo). Ele é capaz de empurrar seleções para baixo em ambos os lados de uma junção , onde é seguro. Essa junção pode, por sua vez, ser transformada em uma aplicação física durante a otimização baseada em custo.

    O efeito de tudo isso é fazer parecer que o otimizador empurrou um predicado para o lado interno de uma aplicação:

    1. Aplicação escrita transformada em uma junção.
    2. Predicado(s) pressionado(s) em ambos os lados da junção.
    3. Junte-se transformado em um aplicativo.

    Deixe-me mostrar-lhe um exemplo:

    DECLARE @T1 table (pk integer PRIMARY KEY, c1 integer NOT NULL INDEX ic1);
    DECLARE @T2 table (fk integer NOT NULL, c2 integer NOT NULL, PRIMARY KEY (fk, c2));
    
    SELECT 
        T1.*,
        T2.*
    FROM @T1 AS T1
    CROSS APPLY 
    (
        SELECT T2.* 
        FROM @T2 AS T2
        WHERE T2.fk = T1.pk
    ) AS T2
    WHERE 
        1 = 1
        AND T1.c1 = 1
        AND T2.c2 = 2;
    

    plano com predicados empurrados para baixo

    Se você observar cuidadosamente o plano, verá o predicado em T2 enviado para a busca do lado interno e a junção de loop aninhada é uma aplicação (tem referências externas ). Isso só foi possível porque o otimizador foi capaz de reescrever a aplicação como uma junção inicialmente, enviar os predicados e, em seguida, transformar novamente em uma aplicação posteriormente.

    Podemos desabilitar a reescrita de aplicação para junção usando o sinalizador de rastreamento não documentado 9114:

    DECLARE @T1 table (pk integer PRIMARY KEY, c1 integer NOT NULL INDEX ic1);
    DECLARE @T2 table (fk integer NOT NULL, c2 integer NOT NULL, PRIMARY KEY (fk, c2));
    
    SELECT 
        T1.*,
        T2.*
    FROM @T1 AS T1
    CROSS APPLY 
    (
        SELECT T2.* 
        FROM @T2 AS T2
        WHERE T2.fk = T1.pk
    ) AS T2
    WHERE 
        1 = 1
        AND T1.c1 = 1
        AND T2.c2 = 2
    OPTION (QUERYTRACEON 9114);
    

    Isso significa que só SELonApplypode ser usado, o que só empurra para a mesa de acionamento A:

    plano com predicado preso

    Observe que a parte da seleção em T2.c2 está 'presa' acima do apply, em um filtro. (A busca do lado interno está apenas na igualdade fk/pk especificada dentro do apply.)


    O otimizador é construído com base em princípios relacionais. Ele aprecia um design de esquema relacional e consultas que usam construções relacionais. Aplicar (junção lateral) é uma extensão relativamente nova. O otimizador conhece muito mais truques com join do que com apply, daí o esforço inicial para reescrever.

    Quando você usa coisas como aplicar ou o Top não relacional, está implicitamente assumindo mais responsabilidade pela forma final do plano. Em outras palavras, com mais frequência você terá que expressar sua consulta de maneira diferente (como em sua solução alternativa) para obter um bom resultado.


    Para constar, minha preferência seria usar a função com valor de tabela embutida com posicionamento de predicado explícito. Se eu fosse reescrever a visão, eu poderia usar:

    CREATE OR ALTER VIEW dbo.SalesReport_unpartitioned
    AS
    --- Regular sales
    SELECT
        BU.BusinessUnit,
        RS.[Date],
        RS.SKU,
        RS.Quantity,
        RS.Amount
    FROM dbo.BusinessUnits AS BU
    JOIN dbo.Sales_unpartitioned AS RS
        ON RS.BusinessUnit = BU.BusinessUnit
    WHERE 
        RS.SKU LIKE 'T%'
    
    UNION ALL
    
    --- This is a special reporting entry.
    SELECT
        BU.BusinessUnit,
        SR.[Date],
        SR.SKU,
        SR.Quantity,
        SR.Amount
    FROM dbo.BusinessUnits AS BU
    JOIN dbo.Sales_unpartitioned AS SR
        ON SR.BusinessUnit = BU.BusinessUnit
    WHERE
        1 = 1
        AND SR.SKU LIKE 'S%'
        --- We only want to see today's row.
        AND SR.[Date] = CONVERT(date, SYSDATETIME())
        --- In case of duplicates, get the row with the first "SKU".
        AND SR.SKU =
        (
            SELECT 
                MIN(SR2.SKU) 
            FROM dbo.Sales_unpartitioned AS SR2
            WHERE 
                SR2.BusinessUnit = SR.BusinessUnit
                AND SR2.[Date] = SR.[Date]
                AND SR2.SKU LIKE 'S%'
        );
    GO
    

    Para a consulta de teste fornecida:

    DECLARE @from date='2021-01-01', @to date='2021-12-31';
    
    SELECT *
    FROM dbo.SalesReport_unpartitioned
    WHERE BusinessUnit=16
      AND [Date] BETWEEN @from AND @to
    ORDER BY BusinessUnit, [Date], SKU;
    

    O plano de execução é:

    plano para reescrever a visualização

    A seção laranja é de vendas regulares. A seção amarela é para a entrada de relatório especial.

    • 8
  2. Andrew Sayer
    2021-07-25T13:14:32+08:002021-07-25T13:14:32+08:00

    Este é apenas um daqueles casos em que o otimizador não foi escrito para lidar com esse requisito complicado específico, pois não pode dizer que o envio do datepredicado não está afetando nada na topsubconsulta.

    Existe uma maneira, use uma junção e reescreva top 1como um filtro em uma row_numberanálise particionada. Incluir o BusinessUnitno row_numberparticionamento significa que é legal empurrar para baixo.

    CREATE OR ALTER VIEW dbo.SalesReport_unpartitioned
    AS
    
    SELECT bu.BusinessUnit,
           s.[Date],
           s.SKU,
           s.Quantity,
           s.Amount
    FROM dbo.BusinessUnits AS bu
    JOIN (
        --- Regular sales
        SELECT t.BusinessUnit, t.[Date], t.SKU, t.Quantity, t.Amount
        FROM dbo.Sales_unpartitioned AS t
        WHERE  t.SKU LIKE 'T%'
    
        UNION ALL
    
        --- This is a special reporting entry. We only
        --- want to see today's row. In case of duplicates,
        --- get the row with the first "SKU".
        SELECT t.BusinessUnit, t.[Date], t.SKU, t.Quantity, t.Amount
        FROM  (SELECT s.BusinessUnit, s.[Date], s.SKU, s.Quantity, s.Amount, row_number() over (partition by s.BusinessUnit order by s.SKU) rn
               FROM dbo.Sales_unpartitioned AS s
               WHERE s.[Date]=CAST(SYSDATETIME() AS date)
                 AND s.SKU LIKE 'S%'
              ) t
         WHERE   rn = 1
    ) AS s
    ON bu.BusinessUnit = s.BusinessUnit
    

    Colar o plano

    • 1

relate perguntas

  • Como o Yelp calcula com eficiência a distância no banco de dados?

  • Otimização de consultas

  • Como devo otimizar o armazenamento para esta tabela?

  • DBMS_REDEFINITION vs EXCHANGE PARTITION no oracle

  • Existe uma boa "regra de ouro" para traduzir o custo EXPLAIN para o tempo de execução (relógio de parede)?

Sidebar

Stats

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

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

    • 12 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

    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
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • 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
    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