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 / 96364
Accepted
crokusek
crokusek
Asked: 2015-03-27 09:41:59 +0800 CST2015-03-27 09:41:59 +0800 CST 2015-03-27 09:41:59 +0800 CST

É possível obter plano paralelo baseado em busca para distinto/grupo por?

  • 772

Um exemplo dessa pergunta mostra que o SQL Server escolherá uma varredura de índice completa para resolver uma consulta como esta:

select distinct [typeName] from [types]

Onde [typeName] tem um índice ascendente não clusterizado e não exclusivo. Seu exemplo tem 200 milhões de linhas, mas apenas 76 valores únicos. Parece que um plano de busca seria uma escolha melhor com essa densidade (~ 76 pesquisas binárias múltiplas)?

O caso dele poderia ser normalizado mas o motivo da pergunta é que eu realmente quero resolver algo assim:

select TransactionId, max(CreatedUtc) 
from TxLog 
group by TransactionId

Existe um índice em (TransactionId, MaxCreatedUtc).

Reescrever usando uma fonte normalizada (dt) não altera o plano.

select dt.TransactionId, MaxCreatedUtc
 from [Transaction] dt -- distinct transactions
 cross apply
   (
        select Max(CreatedUtc) as MaxCreatedUtc 
          from TxLog tl
         where tl.TransactionId = dt.TransactionId         
   ) ca

Executar apenas a subconsulta CA como uma UDF escalar mostra um plano de 1 busca.

select max(CreatedUtc) as MaxCreatedUtc
 from Pub.TransactionLog 
 where TransactionID = @TxId;

Usar esse UDF escalar na consulta original parece funcionar, mas perde o paralelismo (problema conhecido com UDFs):

select t.typeName, 
       Pub.ufn_TransactionMaxCreatedUtc(t.TransactionId) as MaxCreatedUtc
  from Pub.[Transaction] t

Planos para aplicação cruzada, apenas UDF, usando UDF

Reescrever usando um TVF Inline o reverte para o plano baseado em varredura.

Da resposta/comentário @ypercube:

select TransactionId, MaxCreatedUtc        
 from Pub.[Transaction]  t
  cross apply
   (
        select top (1) CreatedUtc as MaxCreatedUtc 
        from Pub.TransactionLog l
        where l.TransactionID = t.TransactionId
        order by CreatedUtc desc                     
   ) ca

Planejar usando topo/ordem

Plano parece bom. Sem paralelismo, mas inútil desde tão rápido. Terá que tentar isso em um problema maior em algum momento. Obrigado.

sql-server index
  • 1 1 respostas
  • 485 Views

1 respostas

  • Voted
  1. Best Answer
    Vladimir Baranov
    2015-09-07T04:34:45+08:002015-09-07T04:34:45+08:00

    Eu tenho exatamente a mesma configuração e passei pelos mesmos estágios de reescrever a consulta.

    No meu caso, os nomes e significados das tabelas são um pouco diferentes, mas a estrutura geral é a mesma. Sua tabela Transactionscorresponde à minha tabela PortalElevatorsabaixo. Tem aproximadamente 2.000 linhas. Sua mesa TxLogcorresponde à minha mesa PlaybackStats. Tem aproximadamente 150 milhões de linhas. Tem índice em (ElevatorID, DataSourceRowID), igual a você.

    Executarei várias variantes da consulta nos dados reais e compararei planos de execução, E/S e estatísticas de tempo. Estou usando o SQL Server 2008 Standard.

    GROUP BY com MAX

    SELECT [ElevatorID], MAX([DataSourceRowID]) AS LastItemID
    FROM [dbo].[PlaybackStats]
    GROUP BY [ElevatorID]
    

    agrupar por

    agrupar por estatísticas

    agrupar por io

    O mesmo que para o seu otimizador verifica o índice e agrega os resultados. Lento.

    linha individual

    Vamos ver o que o otimizador faria se eu solicitasse MAXapenas uma linha:

    SELECT MAX([dbo].[PlaybackStats].[DataSourceRowID]) AS LastItemID
    FROM [dbo].[PlaybackStats]
    WHERE [dbo].[PlaybackStats].ElevatorID = 1
    

    Individual

    O Optimizer é inteligente o suficiente para usar o índice e faz uma busca. A propósito, podemos ver que o otimizador usa o TOPoperador, mesmo que a consulta não o tenha. Este é um sinal claro de que os caminhos de otimização de MAXe TOPtêm algo em comum no motor, mas são diferentes como veremos a seguir.

    CROSS APPLY com MAX

    SELECT
        [dbo].[PortalElevators].elevatorsId
        ,LastItemID
    FROM
        [dbo].[PortalElevators]
        CROSS APPLY
        (
            SELECT MAX([dbo].[PlaybackStats].[DataSourceRowID]) AS LastItemID
            FROM [dbo].[PlaybackStats]
            WHERE [dbo].[PlaybackStats].ElevatorID = [dbo].[PortalElevators].elevatorsId
        ) AS CA
    ;
    

    aplicação cruzada com max

    aplicação cruzada com estatísticas máximas

    aplicação cruzada com max io

    O Optimizer ainda verifica o índice. Não é inteligente o suficiente converter MAXe TOPdigitalizar em buscas aqui. Lento. Não pensei nessa variante originalmente, minha próxima tentativa foi UDF escalar.

    UDF escalar

    Eu vi que o plano para obter MAXuma linha individual tinha busca de índice, então coloquei essa consulta simples em uma UDF escalar.

    CREATE FUNCTION [dbo].[GetElevatorLastID]
    (
        @ParamElevatorID int
    )
    RETURNS bigint
    AS
    BEGIN
        DECLARE @Result bigint;
        SELECT @Result = MAX([dbo].[PlaybackStats].[DataSourceRowID])
        FROM [dbo].[PlaybackStats]
        WHERE [dbo].[PlaybackStats].ElevatorID = @ParamElevatorID;
        RETURN @Result;
    END
    
    SELECT
        [dbo].[PortalElevators].elevatorsId
        ,[dbo].[GetElevatorLastID]([dbo].[PortalElevators].elevatorsId) AS LastItemID
    FROM
        [dbo].[PortalElevators]
    ;
    

    udf

    estatísticas udf

    udf io

    Ele corre rápido. Pelo menos, muito mais rápido do que Group by. Infelizmente, o plano de execução não mostra os detalhes do UDF e, o que é ainda pior, não mostra as estatísticas reais do IO (não inclui o IO gerado pelo UDF). Você precisa executar o Profiler para ver todas as chamadas da função e suas estatísticas. Este plano mostra apenas 6 leituras. O plano para linha individual tem 4 leituras, então o número real seria próximo a: 6 + 2779 * 4 = 6 + 11,116 = 11,122.

    CROSS APPLY com TOP

    Eventualmente, descobri o CROSS APPLYe como pode ser aplicado ;-) neste caso.

    SELECT
        [dbo].[PortalElevators].elevatorsId
        ,LastItemID
    FROM
        [dbo].[PortalElevators]
        CROSS APPLY
        (
            SELECT TOP(1) [dbo].[PlaybackStats].[DataSourceRowID] AS LastItemID
            FROM [dbo].[PlaybackStats]
            WHERE [dbo].[PlaybackStats].ElevatorID = [dbo].[PortalElevators].elevatorsId
            ORDER BY [dbo].[PlaybackStats].[DataSourceRowID] DESC
        ) AS CA
    ;
    

    aplique cruzado com top

    aplicação cruzada com as principais estatísticas

    cross apply com top io

    Aqui, o otimizador é inteligente o suficiente para fazer aproximadamente 2.000 buscas. Você pode ver que o número de leituras é muito menor do que para group by. Velozes.

    Curiosamente, o número de leituras aqui (11.850) é um pouco mais do que as leituras que estimei com UDF (11.122). As estatísticas de IO da tabela CROSS APPLYtêm 11.844 leituras e 2.779 contagens de varredura da tabela grande, o que fornece 11,844 / 2,779 ~= 4.26leituras por busca de índice. Muito provavelmente, as buscas para alguns valores usam 4 leituras e para outros 5, com média de 4,26. Existem 2.779 buscas, mas há valores apenas para 2.130 linhas. Como eu disse, é difícil obter um número real de leituras com UDF sem o profiler.

    CTE recursivo

    Como foi apontado nos comentários, Paul White descreveu um método Recursive Index Skip Scan para encontrar valores distintos em uma tabela grande sem executar uma varredura de índice completa, mas fazer buscas de índice recursivamente. Para iniciar a recursão, precisamos encontrar o valor MINou MAXpara uma âncora e, em seguida, cada etapa da recursão adiciona o próximo valor, um por um. O post explica em detalhes.

    WITH RecursiveCTE
    AS
    (
        -- Anchor
        SELECT TOP (1) [ElevatorID], [DataSourceRowID]
        FROM [dbo].[PlaybackStats]
        ORDER BY [ElevatorID] DESC, [DataSourceRowID] DESC
    
        UNION ALL
    
        -- Recursive
        SELECT R.[ElevatorID], R.[DataSourceRowID]
        FROM
        (
            -- Number the rows
            SELECT
                T.[ElevatorID], T.[DataSourceRowID]
                ,ROW_NUMBER() OVER (ORDER BY T.[ElevatorID] DESC, T.[DataSourceRowID] DESC) AS rn
            FROM
                [dbo].[PlaybackStats] AS T
                INNER JOIN RecursiveCTE AS R ON R.[ElevatorID] > T.[ElevatorID]
        ) AS R
        WHERE
            -- Only the row that sorts lowest
            R.rn = 1
    )
    SELECT [ElevatorID], [DataSourceRowID]
    FROM RecursiveCTE
    OPTION (MAXRECURSION 0);
    

    recursivo

    estatísticas recursivas

    io recursivo

    É bastante rápido, embora execute quase o dobro da quantidade de leituras do CROSS APPLY. Faz 12.781 leituras Worktablee 8.524 em PlaybackStats. Por outro lado, ele realiza tantas buscas quantos forem os valores distintos na tabela grande. CROSS APPLYwith TOPexecuta tantas buscas quanto as linhas da pequena tabela. No meu caso, a tabela pequena possui 2.779 linhas, mas a tabela grande possui apenas 2.130 valores distintos.

    Resumo

                             Logical Reads       Duration
    CROSS APPLY with MAX           482,121          6,604
    GROUP BY with MAX              482,123          6,581
    Scalar UDF                    ~ 11,122            728
    Recursive                       21,305             30
    CROSS APPLY with TOP            11,850              9 (nine!)
    

    Executei cada consulta três vezes e escolhi o melhor momento. Não houve leituras físicas.

    Conclusão

    Neste caso especial de greatest-n-per-groupproblema temos:

    • n=1;
    • o número de grupos é muito menor que o número de linhas em uma tabela;
    • há índice apropriado;

    Dois melhores métodos são:

    1. Caso tenhamos uma pequena tabela com a lista de grupos, o melhor método é CROSS APPLYcom TOP.

    2. No caso de termos apenas uma tabela grande, o melhor método é Recursive Index Skip Scan.

    • 11

relate perguntas

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

  • Quanto "Padding" coloco em meus índices?

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

  • O que significa "índice" em RDBMSs? [fechado]

  • Como criar um índice condicional no MySQL?

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