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 / 219575
Accepted
Jez
Jez
Asked: 2018-10-09 12:56:05 +0800 CST2018-10-09 12:56:05 +0800 CST 2018-10-09 12:56:05 +0800 CST

Como posso tornar essa consulta aninhada mais eficiente?

  • 772

Eu tenho 3 tabelas: Room, Conference, e Participant. Roomtem muitos Conferences, e Conferencetem muitos Participants. Eu preciso que minha consulta exiba os campos de Room, bem como o número de associados Conferencesque ele possui e a soma do número de Participants associados que cada um Conferencepossui. Aqui está uma versão reduzida da SELECTconsulta que escrevi para obter essas informações; primeiro, selecionei o ID da sala:

SELECT TOP(1000)
  rm.[Id]
FROM
  [Room] rm
LEFT JOIN (
  SELECT
    conf.[Id] AS [ConferenceId],
    MIN(conf.[Name]) AS [ConferenceName],
    MIN(conf.[RoomId]) AS [RoomId],
    COUNT(part.[Id]) AS CalcConferenceParticipantCount
  FROM
    [Conference] conf
  LEFT JOIN
    [Participant] part on part.[ConferenceId] = conf.[Id]
  GROUP BY
    conf.[Id]
  ) confData ON confData.[RoomId] = rm.[Id]
GROUP BY
  rm.[Id]

Isso foi muito rápido, pois o SQL Server conseguiu extrair os dados Roome praticamente ignorar a subconsulta (consulte Avaliação 1 - Avaliação 4 na imagem abaixo). Em seguida, adicionei no ConferenceNamecampo da subconsulta, bem como uma contagem do número de conferências por sala:

SELECT TOP(1000)
  rm.[Id],
  COUNT(confData.[ConferenceId]) AS CalcRoomConferenceCount,
  MIN(confData.[ConferenceName])
FROM
  [Room] rm
LEFT JOIN (
  SELECT
    conf.[Id] AS [ConferenceId],
    MIN(conf.[Name]) AS [ConferenceName],
    MIN(conf.[RoomId]) AS [RoomId],
    COUNT(part.[Id]) AS CalcConferenceParticipantCount
  FROM
    [Conference] conf
  LEFT JOIN
    [Participant] part on part.[ConferenceId] = conf.[Id]
  GROUP BY
    conf.[Id]
  ) confData ON confData.[RoomId] = rm.[Id]
GROUP BY
  rm.[Id]

Isso desacelerou um pouco a consulta, por um fator de cerca de 100 (veja Teste 5 - Teste 7 na imagem abaixo). Em seguida, adicionei a contagem de participantes da subconsulta, o que significa que havia 2 níveis de funções agregadas sendo usadas:

SELECT TOP(1000)
  rm.[Id],
  COUNT(confData.[ConferenceId]) AS CalcRoomConferenceCount,
  MIN(confData.[ConferenceName]),
  SUM(confData.[CalcConferenceParticipantCount]) AS CalcRoomParticipantCount
FROM
  [Room] rm
LEFT JOIN (
  SELECT
    conf.[Id] AS [ConferenceId],
    MIN(conf.[Name]) AS [ConferenceName],
    MIN(conf.[RoomId]) AS [RoomId],
    COUNT(part.[Id]) AS CalcConferenceParticipantCount
  FROM
    [Conference] conf
  LEFT JOIN
    [Participant] part on part.[ConferenceId] = conf.[Id]
  GROUP BY
    conf.[Id]
  ) confData ON confData.[RoomId] = rm.[Id]
GROUP BY
  rm.[Id]

Isso desacelerou ainda mais a consulta por um fator de cerca de 4 (veja Ensaio 8 - Ensaio 10 na imagem abaixo). Aqui estão as estatísticas do cliente com dados sobre os 10 testes:

Estatísticas do cliente

Aqui está o plano de consulta da consulta lenta: https://www.brentozar.com/pastetheplan/?id=SJpyeec5Q

Existe uma maneira de tornar esse tipo de consulta - onde calculo uma agregação da agregação de uma subconsulta - mais eficiente?

sql-server performance
  • 1 1 respostas
  • 1263 Views

1 respostas

  • Voted
  1. Best Answer
    Joe Obbish
    2018-10-10T20:14:54+08:002018-10-10T20:14:54+08:00

    Eu zombei de dados observando as contagens de linhas em suas tabelas, dando a elas uma distribuição de dados uniforme e fazendo suposições sobre o esquema:

    DROP TABLE IF EXISTS [Room];
    
    CREATE TABLE [Room] (
        [Id] BIGINT NOT NULL,
        FILLER VARCHAR(200) NOT NULL,
        PRIMARY KEY ([Id])
    );
    
    INSERT INTO [Room] WITH (TABLOCK)
    SELECT TOP (3088) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), REPLICATE('Z', 200)
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
    OPTION (MAXDOP 1);
    
    
    DROP TABLE IF EXISTS [Conference];
    
    CREATE TABLE [Conference] (
        [Id] BIGINT NOT NULL,
        [Name] VARCHAR(30) NOT NULL,
        [RoomId] BIGINT NOT NULL,
        FILLER VARCHAR(200) NOT NULL,
        PRIMARY KEY ([Id])
    );
    
    
    INSERT INTO [Conference] WITH (TABLOCK)
    SELECT RN
    , 'MY FAVORITE MEETING ROOM'
    , 1 + RN % 3088
    , REPLICATE('Z', 200)
    FROM
    (
        SELECT TOP (97413) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
        FROM master..spt_values t1
        CROSS JOIN master..spt_values t2
    ) q
    OPTION (MAXDOP 1);
    
    
    DROP TABLE IF EXISTS [Participant];
    
    CREATE TABLE [Participant] (
        [Id] BIGINT NOT NULL,
        [ConferenceId] BIGINT NOT NULL,
        FILLER VARCHAR(200) NOT NULL,
        PRIMARY KEY ([Id])
    );
    
    
    INSERT INTO [Participant] WITH (TABLOCK)
    SELECT RN
    , 1 + RN % 97413
    , REPLICATE('Z', 200)
    FROM
    (
        SELECT TOP (235323) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
        FROM master..spt_values t1
        CROSS JOIN master..spt_values t2
    ) q
    OPTION (MAXDOP 1);
    
    
    CREATE INDEX NCI_Part ON [Participant] ([ConferenceId]) INCLUDE (Id);
    

    A suposição mais importante que fiz sobre o esquema é que a Idcoluna é a chave primária da [Conference]tabela. Isso parecia razoável, dado o plano de consulta e os nomes de índice envolvidos.

    Na minha máquina, recebo o mesmo plano de consulta que você, mas minha consulta inicial leva apenas 163 ms de CPU. Presumo que as diferenças se resumem a diferenças de hardware, distribuição de dados e ao fato de não estar retornando dados ao cliente.

    A primeira coisa que me chamou atenção é o desnecessário GROUP BYem sua confDatatabela derivada. Idé a chave primária da tabela para que você não precise de todos os agregados. Com os índices certos (que você já tem para este caso em particular), as subconsultas não são necessariamente uma coisa ruim. Reescrevendo o que você precisa remover o GROUP BY:

    SELECT TOP(1000)
      rm.[Id],
      COUNT(confData.[ConferenceId]) AS CalcRoomConferenceCount,
      MIN(confData.[ConferenceName]),
      SUM(confData.[CalcConferenceParticipantCount]) AS CalcRoomParticipantCount
    FROM
      [Room] rm
    LEFT JOIN (
      SELECT
        conf.[Id] AS [ConferenceId],
        conf.[Name] AS [ConferenceName],
        conf.[RoomId] AS [RoomId],
        (
            SELECT COUNT(part.[Id])
            FROM [Participant] part
            WHERE part.[ConferenceId] = conf.[Id]
        ) AS CalcConferenceParticipantCount
      FROM
        [Conference] conf
      ) confData ON confData.[RoomId] = rm.[Id]
    GROUP BY
      rm.[Id]
    OPTION (USE HINT('FORCE_LEGACY_CARDINALITY_ESTIMATION'));
    

    Isso resulta na agregação de fluxo sendo empurrada ainda mais para o plano:

    subconsulta

    O plano carregado leva 113 ms de CPU. Os mesmos operadores estão presentes, mas alguns deles processam menos linhas, o que economiza tempo. Você pode tornar essa consulta mais eficiente definindo um índice de cobertura no [Conference]com Idcomo a chave de índice. Isso pode parecer uma coisa estranha de se fazer, mas sua verificação de índice clusterizado leva 10% do tempo geral de consulta e provavelmente inclui colunas que você não precisa.

    Se você deseja tornar a consulta mais rápida, também pode considerar uma exibição indexada. Por que realizar a agregação sempre que você pode definir uma exibição indexada simples para fazer isso por você?

    CREATE VIEW IndexedViewOnParticipant WITH SCHEMABINDING
    AS
    SELECT [ConferenceId], COUNT_BIG([Id]) CntId, COUNT_BIG(*) Cnt
    FROM dbo.[Participant]
    GROUP BY [ConferenceId];
    
    GO
    
    CREATE UNIQUE CLUSTERED INDEX CI ON IndexedViewOnParticipant ([ConferenceId]);
    

    Isso resultará em um pouco mais de espaço e um pouco de sobrecarga ao fazer DML na mesa. No geral, eu diria que é um bom caso de uso para uma exibição indexada. Reescrevendo a consulta novamente:

    SELECT TOP(1000)
      rm.[Id],
      COUNT(confData.[ConferenceId]) AS CalcRoomConferenceCount,
      MIN(confData.[ConferenceName]),
      SUM(confData.[CalcConferenceParticipantCount]) AS CalcRoomParticipantCount
    FROM 
      [Room] rm
    LEFT JOIN (
      SELECT
        conf.[Id] AS [ConferenceId],
        conf.[Name] AS [ConferenceName],
        conf.[RoomId] AS [RoomId],
        (
            SELECT CntId
            FROM IndexedViewOnParticipant part WITH (NOEXPAND)
            WHERE part.[ConferenceId] = conf.[Id]
        ) AS CalcConferenceParticipantCount
      FROM
        [Conference] conf
      ) confData ON confData.[RoomId] = rm.[Id]
    GROUP BY
      rm.[Id]
    OPTION (USE HINT('FORCE_LEGACY_CARDINALITY_ESTIMATION'));
    

    O SQL Server concorda com minha avaliação de que é uma boa ideia e o tempo de CPU cai para 78 ms .

    Na minha máquina consegui deixar a consulta ainda mais rápida, mas isso está começando a entrar em otimizações que são um pouco arriscadas porque podem exigir uma LOOP JOINdica. Essa dica pode não ser uma boa ideia, pois sua consulta ou os dados na tabela mudam. Também pode não ser um bom ajuste para o seu hardware. A ideia por trás dessa abordagem é criar um índice adequado [Conference]e aproveitar ao máximo o TOPcom um plano que só faz loops aninhados. Aqui está o índice que eu adicionei:

    CREATE INDEX NCI_Conf ON [Conference] ([RoomId]) INCLUDE ([Name]);
    

    A execução da mesma consulta de antes com uma LOOP JOINdica me deu o seguinte plano:

    dica de junção de loop

    Essa consulta levou apenas 58 ms de tempo de CPU. Vale a pena mencionar que notei que solicitar o plano real adiciona um pouco de sobrecarga relativa neste estágio. Todas as outras otimizações possíveis que me vêm à mente não são seguras para produção, então vou parar por aqui.

    Como pensamento final, você realmente deseja retornar 1.000 linhas arbitrárias e o nome mínimo da conferência? Essas informações são úteis para seus usuários finais?

    • 2

relate perguntas

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

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

  • Onde posso encontrar o log lento do mysql?

  • Como posso otimizar um mysqldump de um banco de dados grande?

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