JOIN consultas x várias consultas


180

As consultas JOIN são mais rápidas que as várias consultas? (Você executa sua consulta principal e, em seguida, executa muitos outros SELECTs com base nos resultados da consulta principal)

Estou perguntando porque JUNTAR a eles complicaria MUITO o design do meu aplicativo

Se forem mais rápidos, alguém pode se aproximar mais ou menos em quanto? Se é 1,5x, eu não ligo, mas se é 10x, acho que sim.


Eu suponho que eles seriam mais rápidos. Sei que um INSERT comparado a 10 consultas INSERT individuais é muito mais rápido.
alex

1
Pode ser importante se suas várias consultas estão dentro de um procedimento armazenado ou se são originárias do aplicativo (edite sua pergunta com essas informações). O primeiro será muito mais rápido que o posterior.
colithium

Respostas:


82

Isso é muito vago para fornecer uma resposta relevante ao seu caso específico. Isso depende de muitas coisas. Jeff Atwood (fundador deste site) realmente escreveu sobre isso . Na maioria das vezes, se você tem os índices corretos e faz corretamente seus JOINs, geralmente será mais rápido fazer uma viagem do que várias.


2
se você estiver juntando 3 ou mais tabelas em chaves diferentes, geralmente os bancos de dados (por exemplo, mysql) podem usar apenas um índice por tabela, o que significa que talvez uma das junções seja rápida (e use um índice), enquanto as outras serão extremamente lentas. Para várias consultas, você pode otimizar os índices a serem usados ​​para cada consulta.
precisa saber é o seguinte

4
Eu acho que isso depende da sua definição de "mais rápido" ... por exemplo, 3 junções internas de PK podem girar mais rápido que 4 viagens de ida e volta, por causa da sobrecarga da rede e porque você precisa parar e preparar e enviar cada consulta após o a consulta anterior é concluída. No entanto, se você fizer o benchmark de um servidor sob carga, na maioria dos casos, as junções levarão mais tempo de CPU do que as consultas PK, além de causar mais sobrecarga na rede.
mindplay.dk

97

Para junções internas, uma única consulta faz sentido, pois você só obtém linhas correspondentes. Para associações à esquerda, várias consultas são muito melhores ... veja o seguinte benchmark que eu fiz:

  1. Consulta única com 5 junções

    consulta: 8.074508 segundos

    tamanho do resultado: 2268000

  2. 5 consultas seguidas

    tempo combinado da consulta: 0,00262 segundos

    tamanho do resultado: 165 (6 + 50 + 7 + 12 + 90)

.

Observe que obtemos os mesmos resultados nos dois casos (6 x 50 x 7 x 12 x 90 = 2268000)

junções esquerdas usam exponencialmente mais memória com dados redundantes.

O limite de memória pode não ser tão ruim se você fizer apenas uma junção de duas tabelas, mas geralmente três ou mais e se tornar consultas diferentes.

Como observação lateral, meu servidor MySQL está ao lado de meu servidor de aplicativos ... portanto, o tempo de conexão é insignificante. Se o seu tempo de conexão for em segundos, talvez haja um benefício

Frank


31
Se deixarmos de lado o pequeno e irritante fato de que ninguém em sã consciência faz uma junção cruzada entre 5 tabelas (por esse mesmo motivo, e na maioria dos casos isso simplesmente não faz sentido ), sua "referência" pode ter algum mérito . Mas as junções esquerda ou interna são a norma, geralmente por chave (tornando a recuperação muito mais rápida), e a duplicação de dados geralmente é muito, muito menor do que você está imaginando.
cHao

12
@cHao diz quem? Eu apenas procurei o SMF e o phpBB e vi JOINs entre 3 tabelas - se você adicionar plug-ins ou modificações, eles poderiam facilmente ser adicionados a isso. Qualquer tipo de aplicativo grande tem potencial para muitos JOINs. Indiscutivelmente, um ORM mal escrito / mal usado pode JUNTAR-se a tabelas que ele realmente não precisa (talvez até todas as tabelas).
Natalie Adams

5
@ NathanAdams: As junções esquerda e interna não são ruins. (De fato, se você não está juntando tabelas aqui e ali, está fazendo SQL errado.) O que eu estava falando é sobre junções cruzadas , que quase sempre são indesejáveis ​​mesmo entre duas tabelas, sem falar no 5 - e o que seria ser a única maneira de obter os resultados "2268000" totalmente falsos mencionados acima.
cHao 24/11

2
Veja os resultados, no entanto. "tamanho do resultado: 2268000" versus "tamanho do resultado: 165". Eu acho que sua desaceleração com os JOINs é porque seus registros têm um relacionamento um-para-muitos, enquanto que se eles tivessem um relacionamento um-para-um, o JOIN seria absolutamente muito mais rápido e certamente não teria resultado. tamanho maior que o SELECT.
HoldOffHunger 22/03

3
@cHao Obviamente você não conheceu Magento no momento da sua primeira comentário
vitoriodachef

26

Esta pergunta é antiga, mas faltam alguns parâmetros de referência. Comparei o JOIN com seus 2 concorrentes:

  • N + 1 consultas
  • 2 consultas, a segunda usando uma WHERE IN(...)ou equivalente

O resultado é claro: no MySQL, JOINé muito mais rápido. As consultas N + 1 podem diminuir drasticamente o desempenho de um aplicativo:

JOIN vs WHERE IN vs N + 1

Ou seja, a menos que você selecione muitos registros que apontam para um número muito pequeno de registros estrangeiros distintos. Aqui está uma referência para o caso extremo:

JOIN vs N + 1 - todos os registros apontando para o mesmo registro estrangeiro

É muito improvável que isso aconteça em um aplicativo típico, a menos que você esteja ingressando em um relacionamento com muitos, nesse caso a chave estrangeira está na outra tabela e você está duplicando os dados da tabela principal muitas vezes.

Leve embora:

  • Para relacionamentos * para um, sempre use JOIN
  • Para relacionamentos * para muitos, uma segunda consulta pode ser mais rápida

Veja meu artigo no Medium para obter mais informações.


22

Na verdade, eu mesmo cheguei a essa pergunta procurando uma resposta e, depois de ler as respostas, só posso concordar que a melhor maneira de comparar o desempenho das consultas ao banco de dados é obter números do mundo real, porque há muitas variáveis ​​a serem levadas em consideração MAS, também acho que comparar os números entre eles não leva a nada em quase todos os casos. O que quero dizer é que os números devem sempre ser comparados com um número aceitável e definitivamente não são comparados entre si.

Eu posso entender se uma maneira de consultar leva, digamos, 0,02 segundos e a outra leva 20 segundos, é uma enorme diferença. Mas e se uma maneira de consultar demorar 0,0000000002 segundos e a outra demorar 0,0000002 segundos? Nos dois casos, uma maneira é gritante 1000 vezes mais rápida que a outra, mas ainda é realmente "gritante" no segundo caso?

Resumindo, na minha opinião pessoal: se tiver um bom desempenho, escolha a solução mais fácil.


4
Isso, é claro, dependendo se você está planejando ou não o dimensionamento. Porque quando o Facebook começou, tenho certeza de que eles tinham esse tipo de consulta, mas tinham em mente a escala e optaram pela solução mais eficiente, embora possivelmente mais complexa.
Dudewad

@dudewad Faz sentido. Tudo depende do que você precisa, no final.
Valentin Flachsel

4
Haha yeah ... porque no google 1 nanossegundo perdido é literalmente igual a algo como 10 bilhões de trilhões de dólares ... mas isso é apenas um boato.
Dudewad

2
@dudewad Na verdade, quando o Facebook começou, eu garanto que eles foram com a solução mais simples. Zuckerberg disse que programou a primeira versão em apenas duas semanas. As empresas iniciantes precisam se mover rapidamente para competir e as que sobrevivem geralmente não se preocupam com o dimensionamento até que realmente precisem. Depois refatoram as coisas depois de terem milhões de dólares em investimentos e podem contratar programadores rockstar especializados em desempenho. No seu ponto de vista, eu esperaria que o Facebook busque a solução mais complexa para obter ganhos de desempenho mínimos agora, mas a maioria de nós não está programando o Facebook.
dallin

15

Fez um teste rápido selecionando uma linha de uma tabela de 50.000 linhas e juntando-se a uma linha de uma tabela de 100.000 linhas. Basicamente, parecia:

$id = mt_rand(1, 50000);
$row = $db->fetchOne("SELECT * FROM table1 WHERE id = " . $id);
$row = $db->fetchOne("SELECT * FROM table2 WHERE other_id = " . $row['other_id']);

vs

$id = mt_rand(1, 50000);
$db->fetchOne("SELECT table1.*, table2.*
    FROM table1
    LEFT JOIN table1.other_id = table2.other_id
    WHERE table1.id = " . $id);

O método de seleção dois levou 3,7 segundos para 50.000 leituras, enquanto o JOIN levou 2,0 segundos no meu computador lento em casa. INNER JOIN e LEFT JOIN não fizeram diferença. A busca de várias linhas (por exemplo, usando IN SET) produziu resultados semelhantes.


1
Talvez a diferença possa mudar de outra maneira se você selecionar uma página de linhas (como 20 ou 50) como se fosse uma grade típica de exibição na web e comparar LEFT JOIN único com duas consultas - selecionar 2 ou 3 identificadores com alguns critérios WHERE e depois executar o outro SELECT consulta com IN ().
JustAMartin

O ID das colunas e o other_id são indexados?
Aarish Ramesh

11

A verdadeira questão é: esses registros têm um relacionamento um para um ou um para muitos ?

Resposta TLDR:

Se um para um, use uma JOINdeclaração.

Se um para muitos, use uma (ou muitas) SELECTinstruções com a otimização de código do lado do servidor.

Por que e como usar o SELECT para otimização

SELECT'(com várias consultas em vez de junções) em um grande grupo de registros com base em um relacionamento um para muitos produz uma eficiência ideal, pois JOINhá um problema de vazamento de memória exponencial. Pegue todos os dados e use uma linguagem de script do lado do servidor para classificá-los:

SELECT * FROM Address WHERE Personid IN(1,2,3);

Resultados:

Address.id : 1            // First person and their address
Address.Personid : 1
Address.City : "Boston"

Address.id : 2            // First person's second address
Address.Personid : 1
Address.City : "New York"

Address.id : 3            // Second person's address
Address.Personid : 2
Address.City : "Barcelona"

Aqui, estou obtendo todos os registros, em uma instrução select. Isso é melhor que JOIN, o que seria obter um pequeno grupo desses registros, um de cada vez, como subcomponente de outra consulta. Então eu o analiso com o código do servidor que se parece com ...

<?php
    foreach($addresses as $address) {
         $persons[$address['Personid']]->Address[] = $address;
    }
?>

Quando não usar JOIN para otimização

JOINum grande grupo de registros com base em um relacionamento individual com um único registro produz uma eficiência ideal em comparação com várias SELECTinstruções, uma após a outra, que simplesmente obtêm o próximo tipo de registro.

Mas JOINé ineficiente ao obter registros com um relacionamento de um para muitos.

Exemplo: os blogs do banco de dados têm 3 tabelas de interesse, postagem no blog, tag e comentário.

SELECT * from BlogPost
LEFT JOIN Tag ON Tag.BlogPostid = BlogPost.id
LEFT JOIN Comment ON Comment.BlogPostid = BlogPost.id;

Se houver 1 postagem no blog, 2 tags e 2 comentários, você obterá resultados como:

Row1: tag1, comment1,
Row2: tag1, comment2,
Row3: tag2, comment1,
Row4: tag2, comment2,

Observe como cada registro é duplicado. Ok, então, 2 comentários e 2 tags são 4 linhas. E se tivermos 4 comentários e 4 tags? Você não tem 8 linhas - você tem 16 linhas:

Row1: tag1, comment1,
Row2: tag1, comment2,
Row3: tag1, comment3,
Row4: tag1, comment4,
Row5: tag2, comment1,
Row6: tag2, comment2,
Row7: tag2, comment3,
Row8: tag2, comment4,
Row9: tag3, comment1,
Row10: tag3, comment2,
Row11: tag3, comment3,
Row12: tag3, comment4,
Row13: tag4, comment1,
Row14: tag4, comment2,
Row15: tag4, comment3,
Row16: tag4, comment4,

Adicione mais tabelas, mais registros, etc., e o problema aumentará rapidamente para centenas de linhas cheias de dados principalmente redundantes.

Quanto custam essas duplicatas? Memória (no servidor SQL e o código que tenta remover as duplicatas) e recursos de rede (entre o servidor SQL e o servidor de código).

Fonte: https://dev.mysql.com/doc/refman/8.0/en/nested-join-optimization.html ; https://dev.mysql.com/doc/workbench/en/wb-relationship-tools.html


Você perdeu o ponto. Não se trata de um para um (um | muitos). É sobre se os conjuntos de linhas fazem sentido emparelhados. Você está solicitando apenas dois conjuntos de dados tangencialmente relacionados. Se você estava pedindo comentários e, digamos, as informações de contato dos autores, isso faz mais sentido como associação, mesmo que as pessoas possam presumivelmente escrever mais de um comentário.
precisa saber é

@cHao: Obrigado pelo seu comentário. Minha resposta acima é um resumo da documentação MySQL encontrada aqui: dev.mysql.com/doc/workbench/en/wb-relationship-tools.html
HoldOffHunger

Isso não é documentação do MySQL. É documentação para uma ferramenta GUI específica para trabalhar com bancos de dados MySQL. E não oferece nenhuma orientação sobre quando as associações são (ou não são) apropriadas.
Chao

@cHao: Desculpe, eu quis dizer a documentação do MySQL (R) para o MySQL WorkBench (TM), não o MySQL Server (TM).
HoldOffHunger

Pedantismo à parte, a relevância não é clara. Ambos mencionam relacionamentos um para um e um para muitos, mas é aí que o ponto em comum termina. De qualquer maneira, a questão é sobre o relacionamento entre os conjuntos de dados. Junte-se a dois conjuntos não relacionados, você terá todas as combinações dos dois. Divida os dados relacionados em várias seleções e agora você fez várias consultas para obter benefícios duvidosos e começou a fazer o trabalho do MySQL para isso.
Chao

8

Construa consultas e uniões separadas e cronometre cada uma delas - nada ajuda mais do que números do mundo real.

Melhor ainda - adicione "EXPLAIN" ao início de cada consulta. Isso informará quantas subconsultas o MySQL está usando para responder à sua solicitação de dados e quantas linhas varridas para cada consulta.


7

Dependendo da complexidade do banco de dados em comparação com a complexidade do desenvolvedor, pode ser mais simples fazer muitas chamadas SELECT.

Tente executar algumas estatísticas do banco de dados no JOIN e no SELECTS múltiplo. Veja se, no seu ambiente, o JOIN é mais rápido / mais lento que o SELECT.

Por outro lado, se alterá-lo para um JOIN significaria um dia / semana / mês extra de trabalho para desenvolvedor, eu ficaria com vários SELECTs

Felicidades,

BLT


5

Na minha experiência, descobri que geralmente é mais rápido executar várias consultas, especialmente ao recuperar grandes conjuntos de dados.

Ao interagir com o banco de dados de outro aplicativo, como o PHP, há o argumento de uma viagem ao servidor entre várias.

Existem outras maneiras de limitar o número de viagens feitas ao servidor e ainda executar várias consultas que geralmente não são apenas mais rápidas, mas também facilitam a leitura do aplicativo - por exemplo, mysqli_multi_query.

Não sou novato no que diz respeito ao SQL, acho que há uma tendência para os desenvolvedores, especialmente os juniores, gastarem muito tempo tentando escrever junções muito inteligentes porque parecem inteligentes, enquanto existem maneiras inteligentes de extrair dados com aparência simples.

O último parágrafo foi uma opinião pessoal, mas espero que isso ajude. Eu concordo com os outros que dizem que você deveria fazer benchmark. Nenhuma das abordagens é uma bala de prata.


Sim, também devemos considerar não apenas as consultas em si, mas também o processamento de dados dentro do aplicativo. Ao buscar dados com junções externas, há alguma redundância (às vezes pode ser realmente grande) que precisa ser resolvida pelo aplicativo (geralmente em alguma biblioteca ORM); portanto, em resumo, a única consulta SELECT com JOIN pode consumir mais CPU e tempo que dois SELECTs simples
JustAMartin 14/17

4

Se você deve usar uma junção, é antes de tudo uma questão de saber se uma junção faz sentido . Somente nesse ponto o desempenho é algo a ser considerado, pois quase todos os outros casos resultam em desempenho significativamente pior .

As diferenças de desempenho estarão em grande parte relacionadas à relação das informações que você está consultando. As junções funcionam e são rápidas quando os dados estão relacionados e você indexa as coisas corretamente, mas geralmente resultam em redundância e, às vezes, mais resultados do que o necessário. E se seus conjuntos de dados não estiverem diretamente relacionados, colocá-los em uma única consulta resultará no que é chamado de produto cartesiano (basicamente, todas as combinações possíveis de linhas), o que quase nunca é o que você deseja.

Isso geralmente é causado por relacionamentos muitos-para-um-para-muitos. Por exemplo, a resposta do HoldOffHunger mencionou uma única consulta para postagens, tags e comentários. Os comentários estão relacionados a uma postagem, assim como as tags ... mas as tags não estão relacionadas aos comentários.

+------------+     +---------+     +---------+
|  comment   |     |   post  |     |  tag    |
|------------|*   1|---------|1   *|---------|
| post_id    |-----| post_id |-----| post_id |
| comment_id |     | ...     |     | tag_id  |
| user_id    |     |         |     | ...     |
| ...        |     |         |     | ...     |
+------------+     +---------+     +---------+

Nesse caso, é inequivocamente melhor que sejam pelo menos duas consultas separadas. Se você tentar juntar tags e comentários, porque não há uma relação direta entre os dois, você terá todas as combinações possíveis de tag e comentário. many * many == manymany. Além disso, como as postagens e tags não são relacionadas, você pode fazer essas duas consultas em paralelo, levando a um ganho potencial.

Vamos considerar um cenário diferente: você deseja que os comentários sejam anexados a uma postagem e as informações de contato dos comentaristas.

 +----------+     +------------+     +---------+
 |   user   |     |  comment   |     |   post  |
 |----------|1   *|------------|*   1|---------|
 | user_id  |-----| post_id    |-----| post_id |
 | username |     | user_id    |     | ...     |
 | ...      |     | ...        |     +---------+
 +----------+     +------------+

É aqui que você deve considerar uma associação. Além de ser uma consulta muito mais natural, a maioria dos sistemas de banco de dados (incluindo o MySQL) tem muitas pessoas inteligentes dedicando muito trabalho na otimização de consultas como essa. Para consultas separadas, uma vez que cada consulta depende dos resultados da anterior, as consultas não podem ser feitas em paralelo e o tempo total torna-se não apenas o tempo de execução real das consultas, mas também o tempo gasto na busca de resultados, peneirando através deles para IDs para a próxima consulta, vinculando linhas, etc.


Se você recuperar muitas colunas de usuário no segundo cenário (e os mesmos usuários comentarem mais de uma vez), isso ainda deixa em aberto a questão de saber se elas são melhor recuperadas em uma consulta separada.
Adrian Baker

@AdrianBaker: Como eu disse, muitas pessoas inteligentes dedicam muito trabalho. Se eu fosse otimizar meu servidor SQL, minha primeira idéia seria usar a compactação, que eliminaria uma enorme quantidade de redundância sem alterar o código muito mesmo. As otimizações de próximo nível incluiriam reorganizar o resultado em tabelas e enviá-las juntamente com as tuplas de IDs de linha, que a biblioteca do cliente poderia então montar facilmente de lado, conforme necessário.
cHao 16/09/19

Ambas as otimizações podem fazer maravilhas com uma junção para reduzir ou até eliminar a redundância, mas não há muito que possa ajudar nas consultas em série inerentes que você teria que fazer para buscar registros relacionados.
cHao 16/09/19

3

Será mais rápido em termos de taxa de transferência? Provavelmente. Mas também potencialmente bloqueia mais objetos de banco de dados ao mesmo tempo (dependendo do seu banco de dados e seu esquema) e, portanto, diminui a simultaneidade. Na minha experiência, as pessoas geralmente são enganadas pelo argumento "menos viagens de ida e volta ao banco de dados" quando, na realidade, na maioria dos sistemas OLTP em que o banco de dados está na mesma LAN, o gargalo real raramente é a rede.



1

Existem vários fatores, o que significa que não há resposta binária. A questão do que é melhor para o desempenho depende do seu ambiente. A propósito, se sua seleção única com um identificador não for um segundo, algo pode estar errado com sua configuração.

A verdadeira pergunta é como você deseja acessar os dados. As seleções únicas suportam ligação tardia. Por exemplo, se você deseja apenas informações de funcionários, é possível selecionar na tabela Funcionários. Os relacionamentos de chave estrangeira podem ser usados ​​para recuperar recursos relacionados posteriormente e conforme necessário. As seleções já terão uma chave para apontar, portanto devem ser extremamente rápidas e você só precisa recuperar o que precisa. A latência da rede sempre deve ser levada em consideração.

As junções recuperam todos os dados de uma só vez. Se você estiver gerando um relatório ou preenchendo uma grade, pode ser exatamente isso que você deseja. Junções compiladas e otimizadas simplesmente serão mais rápidas do que as seleções únicas nesse cenário. Lembre-se, as junções ad-hoc podem não ser tão rápidas - você deve compilá-las (em um processo armazenado). A resposta rápida depende do plano de execução, que detalha exatamente quais etapas o DBMS executa para recuperar os dados.


0

Sim, uma consulta usando JOINS seria mais rápida. Embora sem conhecer os relacionamentos das tabelas que você está consultando, o tamanho do seu conjunto de dados ou onde estão as chaves primárias, é quase impossível dizer o quanto mais rápido.

Por que não testar os dois cenários, você saberá com certeza ...

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.