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 / 63484
Accepted
JohnnyM
JohnnyM
Asked: 2014-04-19 11:29:12 +0800 CST2014-04-19 11:29:12 +0800 CST 2014-04-19 11:29:12 +0800 CST

No SQL Server, devo forçar um LOOP JOIN no seguinte caso?

  • 772

Normalmente, eu não recomendo o uso de dicas de junção por todos os motivos padrão. Recentemente, no entanto, descobri um padrão em que quase sempre encontro uma junção de loop forçada para ter um desempenho melhor. Na verdade, estou começando a usar e recomendo tanto que queria uma segunda opinião para ter certeza de que não estou perdendo nada. Aqui está um cenário representativo (o código muito específico para gerar um exemplo está no final):

--Case 1: NO HINT
SELECT S.*
INTO #Results
FROM #Driver AS D
JOIN SampleTable AS S ON S.ID = D.ID

--Case 2: LOOP JOIN HINT
SELECT S.*
INTO #Results
FROM #Driver AS D
INNER LOOP JOIN SampleTable AS S ON S.ID = D.ID

SampleTable tem 1 milhão de linhas e seu PK é ID.
A tabela temporária #Driver tem apenas uma coluna, ID, nenhum índice e 50 mil linhas.

O que eu sempre encontro é o seguinte:

Caso 1: NO HINT
Index Scan na SampleTable
Hash Join
Maior duração (média de 333 ms)
Maior CPU (média de 331 ms)
Baixas leituras lógicas (4714)

Caso 2: LOOP JOIN HINT
Index Seek on SampleTable
Loop Join
Menor duração (média de 204ms, 39% menos)
CPU mais baixa (média de 206, 38% menos)
Leituras lógicas muito mais altas (160015, 34X mais)

A princípio, as leituras muito mais altas do segundo caso me assustaram um pouco porque diminuir as leituras costuma ser considerado uma medida decente de desempenho. Mas quanto mais penso sobre o que está realmente acontecendo, isso não me preocupa. Aqui está o meu pensamento:

SampleTable está contido em 4714 páginas, ocupando cerca de 36 MB. O caso 1 verifica todos eles, e é por isso que obtemos 4714 leituras. Além disso, ele deve executar 1 milhão de hashes, que consomem muita CPU e, em última análise, aumentam o tempo proporcionalmente. É todo esse hashing que parece aumentar o tempo no caso 1.

Agora considere o caso 2. Ele não está fazendo nenhum hash, mas em vez disso está fazendo 50.000 buscas separadas, que é o que está aumentando as leituras. Mas quão caras são as leituras comparativamente? Pode-se dizer que, se essas forem leituras físicas, pode ser muito caro. Mas lembre-se de 1) apenas a primeira leitura de uma determinada página pode ser física e 2) mesmo assim, o caso 1 teria o mesmo ou pior problema, pois é garantido que atingirá todas as páginas.

Levando em conta o fato de que ambos os casos precisam acessar cada página pelo menos uma vez, parece ser uma questão de qual é mais rápido, 1 milhão de hashes ou cerca de 155.000 leituras na memória? Meus testes parecem dizer o último, mas o SQL Server escolhe consistentemente o primeiro.

Pergunta

Então, de volta à minha pergunta: devo continuar forçando essa dica LOOP JOIN quando o teste mostra esses tipos de resultados ou estou perdendo alguma coisa na minha análise? Estou hesitante em ir contra o otimizador do SQL Server, mas parece que ele muda para usar uma junção de hash muito antes do que deveria em casos como esses.

Atualização 2014-04-28

Fiz mais alguns testes e descobri que os resultados que estava obtendo acima (em uma VM com 2 CPUs) não podiam ser replicados em outros ambientes (tentei em 2 máquinas físicas diferentes com 8 e 12 CPUs). O otimizador se saiu muito melhor nos últimos casos, a ponto de não haver esse problema pronunciado. Acho que a lição aprendida, que parece óbvia em retrospecto, é que o ambiente pode afetar significativamente o funcionamento do otimizador.

Planos de Execução

Plano de Execução Caso 1 Plano 1 Plano de Execução Caso 2 insira a descrição da imagem aqui

Código para gerar caso de exemplo

------------------------------------------------------------
-- 1. Create SampleTable with 1,000,000 rows
------------------------------------------------------------    

CREATE TABLE SampleTable
    (  
       ID         INT NOT NULL PRIMARY KEY CLUSTERED
     , Number1    INT NOT NULL
     , Number2    INT NOT NULL
     , Number3    INT NOT NULL
     , Number4    INT NOT NULL
     , Number5    INT NOT NULL
    )

--Add 1 million rows
;WITH  
    Cte0 AS (SELECT 1 AS C UNION ALL SELECT 1), --2 rows  
    Cte1 AS (SELECT 1 AS C FROM Cte0 AS A, Cte0 AS B),--4 rows  
    Cte2 AS (SELECT 1 AS C FROM Cte1 AS A ,Cte1 AS B),--16 rows 
    Cte3 AS (SELECT 1 AS C FROM Cte2 AS A ,Cte2 AS B),--256 rows 
    Cte4 AS (SELECT 1 AS C FROM Cte3 AS A ,Cte3 AS B),--65536 rows 
    Cte5 AS (SELECT 1 AS C FROM Cte4 AS A ,Cte2 AS B),--1048576 rows 
    FinalCte AS (SELECT  ROW_NUMBER() OVER (ORDER BY C) AS Number FROM   Cte5)
INSERT INTO SampleTable
SELECT Number, Number, Number, Number, Number, Number
FROM  FinalCte
WHERE Number <= 1000000

------------------------------------------------------------
-- Create 2 SPs that join from #Driver to SampleTable.
------------------------------------------------------------    
GO
IF OBJECT_ID('JoinTest_NoHint') IS NOT NULL DROP PROCEDURE JoinTest_NoHint
GO
CREATE PROC JoinTest_NoHint
AS
    SELECT S.*
    INTO #Results
    FROM #Driver AS D
    JOIN SampleTable AS S ON S.ID = D.ID
GO
IF OBJECT_ID('JoinTest_LoopHint') IS NOT NULL DROP PROCEDURE JoinTest_LoopHint
GO
CREATE PROC JoinTest_LoopHint
AS
    SELECT S.*
    INTO #Results
    FROM #Driver AS D
    INNER LOOP JOIN SampleTable AS S ON S.ID = D.ID
GO

------------------------------------------------------------
-- Create driver table with 50K rows
------------------------------------------------------------    
GO
IF OBJECT_ID('tempdb..#Driver') IS NOT NULL DROP TABLE #Driver
SELECT ID
INTO #Driver
FROM SampleTable
WHERE ID % 20 = 0

------------------------------------------------------------
-- Run each test and run Profiler
------------------------------------------------------------    

GO
/*Reg*/  EXEC JoinTest_NoHint
GO
/*Loop*/ EXEC JoinTest_LoopHint


------------------------------------------------------------
-- Results
------------------------------------------------------------    

/*

Duration CPU   Reads    TextData
315      313   4714     /*Reg*/  EXEC JoinTest_NoHint
309      296   4713     /*Reg*/  EXEC JoinTest_NoHint
327      329   4713     /*Reg*/  EXEC JoinTest_NoHint
398      406   4715     /*Reg*/  EXEC JoinTest_NoHint
316      312   4714     /*Reg*/  EXEC JoinTest_NoHint
217      219   160017   /*Loop*/ EXEC JoinTest_LoopHint
211      219   160014   /*Loop*/ EXEC JoinTest_LoopHint
217      219   160013   /*Loop*/ EXEC JoinTest_LoopHint
190      188   160013   /*Loop*/ EXEC JoinTest_LoopHint
187      187   160015   /*Loop*/ EXEC JoinTest_LoopHint

*/
sql-server
  • 2 2 respostas
  • 8181 Views

2 respostas

  • Voted
  1. Best Answer
    Paul White
    2014-04-19T14:06:22+08:002014-04-19T14:06:22+08:00

    SampleTable está contido em 4714 páginas, ocupando cerca de 36 MB. O caso 1 verifica todos eles, e é por isso que obtemos 4714 leituras. Além disso, ele deve executar 1 milhão de hashes, que consomem muita CPU e, em última análise, aumentam o tempo proporcionalmente. É todo esse hashing que parece aumentar o tempo no caso 1.

    Há um custo inicial para uma junção de hash (criar a tabela de hash, que também é uma operação de bloqueio), mas a junção de hash tem o menor custo teórico por linha dos três tipos de junção física suportados pelo SQL Server, ambos em termos de E/S e CPU. A junção de hash realmente se destaca com uma entrada de compilação relativamente pequena e uma entrada de teste grande. Dito isso, nenhum tipo de junção física é 'melhor' em todos os cenários.

    Agora considere o caso 2. Ele não está fazendo nenhum hash, mas em vez disso está fazendo 50.000 buscas separadas, que é o que está aumentando as leituras. Mas quão caras são as leituras comparativamente? Pode-se dizer que, se essas forem leituras físicas, pode ser muito caro. Mas lembre-se de 1) apenas a primeira leitura de uma determinada página pode ser física e 2) mesmo assim, o caso 1 teria o mesmo ou pior problema, pois é garantido que atingirá todas as páginas.

    Cada busca requer a navegação em uma b-tree até a raiz, o que é computacionalmente caro em comparação com uma única investigação de hash. Além disso, o padrão geral de IO para o lado interno de uma junção de loops aninhados é aleatório, em comparação com o padrão de acesso sequencial da entrada de varredura do lado da sonda para uma junção hash. Dependendo do subsistema de E/S físico subjacente, as leituras sequenciais podem ser mais rápidas do que as leituras aleatórias. Além disso, o mecanismo de leitura antecipada do SQL Server funciona melhor com E/S sequencial, emitindo leituras maiores.

    Levando em conta o fato de que ambos os casos precisam acessar cada página pelo menos uma vez, parece ser uma questão de qual é mais rápido, 1 milhão de hashes ou cerca de 155.000 leituras na memória? Meus testes parecem dizer o último, mas o SQL Server escolhe consistentemente o primeiro.

    O otimizador de consulta do SQL Server faz várias suposições. Uma delas é que o primeiro acesso a uma página feito por uma consulta resultará em um IO físico (a 'suposição de cache frio'). A chance de uma leitura posterior vir de uma página já lida na memória pela mesma consulta é modelada, mas isso não passa de um palpite.

    A razão pela qual o modelo do otimizador funciona dessa maneira é que geralmente é melhor otimizar para o pior caso (é necessário E/S físico). Muitas deficiências podem ser cobertas pelo paralelismo e pela execução de coisas na memória. Os planos de consulta que o otimizador produziria se presumisse que todos os dados estavam na memória poderiam ter um desempenho muito ruim se essa suposição fosse inválida.

    O plano produzido usando a suposição de cache frio pode não funcionar tão bem quanto se um cache quente fosse assumido, mas seu desempenho de pior caso geralmente será superior.

    Devo continuar forçando essa dica LOOP JOIN quando o teste mostra esses tipos de resultados ou estou perdendo alguma coisa na minha análise? Estou hesitante em ir contra o otimizador do SQL Server, mas parece que ele muda para usar uma junção de hash muito antes do que deveria em casos como esses.

    Você deve ter muito cuidado ao fazer isso por dois motivos. Primeiro, as dicas de junção também forçam silenciosamente a ordem de junção física para corresponder à ordem escrita da consulta (como se você também tivesse especificado OPTION (FORCE ORDER). Isso limita severamente as alternativas disponíveis para o otimizador e pode nem sempre ser o que você deseja. OPTION (LOOP JOIN)força loops aninhados joins para a consulta, mas não impõe a ordem de junção por escrito.

    Em segundo lugar, você está supondo que o tamanho do conjunto de dados permanecerá pequeno e a maioria das leituras lógicas virá do cache. Se essas suposições se tornarem inválidas (talvez com o tempo), o desempenho diminuirá. O otimizador de consulta embutido é muito bom em reagir a mudanças nas circunstâncias; remover essa liberdade é algo em que você deve pensar muito.

    No geral, a menos que haja um motivo convincente para forçar as junções de loops, eu o evitaria. Os planos padrão geralmente estão bem próximos do ideal e tendem a ser mais resilientes diante de mudanças nas circunstâncias.

    • 13
  2. Dave Markle
    2014-04-19T15:27:43+08:002014-04-19T15:27:43+08:00

    50.000 linhas unidas em uma tabela de um milhão de linhas parece ser muito para qualquer tabela sem um índice.

    É difícil dizer exatamente o que fazer neste caso, já que ele está tão isolado do problema que você está realmente tentando resolver. Eu certamente espero que não seja um padrão geral em seu código onde você está se juntando a muitas tabelas temporárias não indexadas com quantidades significativas de linhas.

    Tomando o exemplo apenas pelo que diz, por que não colocar um índice em #Driver? O D.ID é verdadeiramente único? Em caso afirmativo, isso é semanticamente equivalente a uma instrução EXISTS, que pelo menos permitirá ao SQL Server saber que você não deseja continuar pesquisando S por valores duplicados de D:

    SELECT S.*
    INTO #Results
    FROM SampleTable S
    WHERE EXISTS (SELECT * #Driver D WHERE S.ID = D.ID);
    

    Resumindo, para esse padrão, eu não usaria uma dica de LOOP. Eu simplesmente não usaria esse padrão. Eu faria um dos seguintes, em ordem de prioridade, se possível:

    • Use um CTE em vez de uma tabela temporária para #Driver, se possível
    • Use um índice não clusterizado exclusivo em #Driver em ID se for exclusivo (supondo que esta seja a única vez que você usa #Driver e que não deseja nenhum dado da tabela em si - se realmente precisar de dados dessa tabela, você pode muito bem torná-lo um índice clusterizado)
    • 0

relate perguntas

  • SQL Server - Como as páginas de dados são armazenadas ao usar um índice clusterizado

  • Preciso de índices separados para cada tipo de consulta ou um índice de várias colunas funcionará?

  • Quando devo usar uma restrição exclusiva em vez de um índice exclusivo?

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

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

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