As consultas individuais são mais rápidas que as associações?

44

Pergunta conceitual: as consultas individuais são mais rápidas que as associações, ou: Devo tentar espremer todas as informações que desejo do lado do cliente em uma instrução SELECT ou apenas usar quantas parecer conveniente?

TL; DR : Se minha consulta ingressada demorar mais do que a execução de consultas individuais, isso é culpa minha ou isso é esperado?

Primeiro, não sou muito conhecedor de bancos de dados, portanto, sou eu, mas notei que, quando preciso obter informações de várias tabelas, é "frequentemente" mais rápido obter essas informações por meio de várias consultas em tabelas individuais (talvez contendo uma junção interna simples) e remende os dados juntos no lado do cliente para tentar escrever uma consulta ingressada (complexa) onde eu possa obter todos os dados em uma consulta.

Eu tentei colocar um exemplo extremamente simples:

SQL Fiddle

Configuração do esquema :

CREATE TABLE MASTER 
( ID INT NOT NULL
, NAME VARCHAR2(42 CHAR) NOT NULL
, CONSTRAINT PK_MASTER PRIMARY KEY (ID)
);

CREATE TABLE DATA
( ID INT NOT NULL
, MASTER_ID INT NOT NULL
, VALUE NUMBER
, CONSTRAINT PK_DATA PRIMARY KEY (ID)
, CONSTRAINT FK_DATA_MASTER FOREIGN KEY (MASTER_ID) REFERENCES MASTER (ID)
);

INSERT INTO MASTER values (1, 'One');
INSERT INTO MASTER values (2, 'Two');
INSERT INTO MASTER values (3, 'Three');

CREATE SEQUENCE SEQ_DATA_ID;

INSERT INTO DATA values (SEQ_DATA_ID.NEXTVAL, 1, 1.3);
INSERT INTO DATA values (SEQ_DATA_ID.NEXTVAL, 1, 1.5);
INSERT INTO DATA values (SEQ_DATA_ID.NEXTVAL, 1, 1.7);
INSERT INTO DATA values (SEQ_DATA_ID.NEXTVAL, 2, 2.3);
INSERT INTO DATA values (SEQ_DATA_ID.NEXTVAL, 3, 3.14);
INSERT INTO DATA values (SEQ_DATA_ID.NEXTVAL, 3, 3.7);

Consulta A :

select NAME from MASTER
where ID = 1

Resultados :

| NAME |
--------
|  One |

Consulta B :

select ID, VALUE from DATA
where MASTER_ID = 1

Resultados :

| ID | VALUE |
--------------
|  1 |   1.3 |
|  2 |   1.5 |
|  3 |   1.7 |

Consulta C :

select M.NAME, D.ID, D.VALUE 
from MASTER M INNER JOIN DATA D ON M.ID=D.MASTER_ID
where M.ID = 1

Resultados :

| NAME | ID | VALUE |
---------------------
|  One |  1 |   1.3 |
|  One |  2 |   1.5 |
|  One |  3 |   1.7 |

Obviamente, não medi nenhum desempenho com isso, mas podemos observar:

  • A consulta A + B retorna a mesma quantidade de informações utilizáveis ​​que a consulta C.
  • A + B deve retornar 1 + 2x3 == 7 "Células de Dados" para o cliente
  • C precisa retornar 3x3 == 9 "Células de Dados" ao cliente, porque com a junção naturalmente incluo alguma redundância no conjunto de resultados.

Generalizando a partir disso (por mais buscado que seja):

Uma consulta ingressada sempre deve retornar mais dados do que as consultas individuais que recebem a mesma quantidade de informações. Como o banco de dados precisa reunir os dados, para conjuntos de dados grandes, pode-se supor que o banco de dados tenha que trabalhar mais em uma única consulta unida do que nas individuais, pois (pelo menos) ele deve retornar mais dados ao cliente.

Daqui resulta que, quando observo que a divisão de uma consulta do lado do cliente em várias consultas produz um melhor desempenho, esse é apenas o caminho a seguir ou seria melhor dizer que eu estraguei a consulta unida?

Martin
fonte
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
Jack Douglas
1
Fiz um benchmark e publiquei os resultados em um artigo no Medium . Eu teria adicionado uma resposta aqui, mas já fiz isso em outra pergunta , e postar a mesma resposta para várias perguntas é desaprovada .
Benjamin

Respostas:

45

As consultas individuais são mais rápidas do que as associações, ou: Devo tentar espremer todas as informações que desejo no lado do cliente em uma instrução SELECT ou apenas usar quantas parecer conveniente?

Em qualquer cenário de desempenho, você precisa testar e medir as soluções para ver qual é mais rápido .

Dito isso, é quase sempre o caso de um conjunto de resultados unidos de um banco de dados ajustado corretamente ser mais rápido e escalar melhor do que retornar as linhas de origem ao cliente e depois juntá-las lá. Em particular, se os conjuntos de entrada forem grandes e o conjunto de resultados for pequeno - pense na seguinte consulta no contexto de ambas as estratégias: junte duas tabelas de 5 GB cada, com um conjunto de resultados de 100 linhas. Isso é extremo, mas você entende meu ponto.

Percebi que quando preciso obter informações de várias tabelas, é "geralmente" mais rápido obter essas informações por meio de várias consultas em tabelas individuais (talvez contendo uma simples junção interna) e juntar os dados no lado do cliente para tentar para escrever uma consulta unida (complexa) na qual eu possa obter todos os dados em uma consulta.

É altamente provável que o esquema ou os índices do banco de dados possam ser aprimorados para atender melhor às consultas que você está fazendo.

Uma consulta ingressada sempre deve retornar mais dados do que as consultas individuais que recebem a mesma quantidade de informações.

Geralmente não é esse o caso. Na maioria das vezes, mesmo que os conjuntos de entradas sejam grandes, o conjunto de resultados será muito menor que a soma das entradas.

Dependendo do aplicativo, conjuntos de resultados de consulta muito grandes retornados ao cliente são uma bandeira vermelha imediata: o que o cliente está fazendo com um conjunto tão grande de dados que não pode ser feito mais perto do banco de dados? Exibir 1.000.000 de linhas para um usuário é altamente suspeito, para dizer o mínimo. A largura de banda da rede também é um recurso finito.

Como o banco de dados precisa reunir os dados, para conjuntos de dados grandes, pode-se supor que o banco de dados precise trabalhar mais em uma única consulta unida do que nas consultas individuais, pois (pelo menos) ele deve retornar mais dados ao cliente.

Não necessariamente. Se os dados forem indexados corretamente, é mais provável que a operação de junção seja realizada com mais eficiência no banco de dados, sem a necessidade de varrer uma grande quantidade de dados. Além disso, os mecanismos de banco de dados relacional são especialmente otimizados em um nível baixo para ingresso ; pilhas de clientes não são.

Daqui resulta que, quando observo que a divisão de uma consulta do lado do cliente em várias consultas produz um melhor desempenho, esse é apenas o caminho a seguir ou seria melhor dizer que eu estraguei a consulta unida?

Como você disse que é inexperiente no que diz respeito a bancos de dados, sugiro aprender mais sobre design de banco de dados e ajuste de desempenho. Tenho certeza de que é aí que o problema está aqui. Também são possíveis consultas SQL gravadas ineficientemente, mas com um esquema simples que é menos provável que seja um problema.

Agora, isso não quer dizer que não há outras maneiras de melhorar o desempenho. Há cenários em que você pode optar por varrer um conjunto de dados de médio a grande porte e devolvê-lo ao cliente se a intenção for usar algum tipo de mecanismo de armazenamento em cache. O armazenamento em cache pode ser ótimo, mas introduz complexidade no seu design. O armazenamento em cache pode até não ser apropriado para o seu aplicativo.

Uma coisa que não foi mencionada em nenhum lugar é manter a consistência nos dados retornados do banco de dados. Se consultas separadas forem usadas, é mais provável (devido a muitos fatores) a devolução de dados inconsistentes, a menos que seja usada uma forma de isolamento de instantâneo para cada conjunto de consultas.

Jon Seigel
fonte
O +1 na largura de banda da rede também é um recurso finito.
Hari Harker
O OP está dizendo que os conjuntos de resultados de dados JOIN são sempre maiores. > Uma consulta unida sempre deve retornar mais dados do que as consultas individuais. Eu acho que isso é objetivamente verdadeiro (para> =), por exemplo, os conjuntos de resultados diferem em tamanho, para mais dados por fio. Você tem um exemplo em que isso não é verdade? Se eu ingressar em Autores -> Postagens e Autores, há um campo chamado "biografia", que é um campo JSON de 1 MB, para um Autor de 100 Postagens, através do fio transmitirei 100 MB vs 1 MB. Isso está errado?
hytromo 25/06
6

Claro, eu não medi nenhum desempenho com esses

Você cria um bom código de exemplo. Você olhou para o tempo no SQL Fiddle? Até mesmo alguns breves testes de desempenho não-científicos mostram que a consulta três em sua demonstração leva aproximadamente a mesma quantidade de tempo para ser executada como a consulta um ou dois separadamente. Um e dois combinados levam cerca de duas vezes o tempo que três e isso é antes de qualquer junção do lado do cliente ser realizada.

À medida que você aumenta os dados, a velocidade da consulta um e dois diverge, mas a associação ao banco de dados ainda é mais rápida.

Você também deve considerar o que aconteceria se a junção interna estivesse eliminando dados.

Leigh Riffel
fonte
2

O otimizador de consulta também deve ser considerado. Seu papel é pegar seu SQL declarativo e convertê-lo em etapas processuais. Para encontrar a combinação mais eficiente de etapas processuais, ele examinará as combinações de uso, classificação, armazenamento em cache de conjuntos de resultados intermediários e todo tipo de outras coisas também. O número de permutações pode ser extremamente grande, mesmo com consultas bastante simples.

Grande parte do cálculo feito para encontrar o melhor plano é direcionado pela distribuição de dados nas tabelas. Essas distribuições são amostradas e armazenadas como objetos de estatística. Se estiverem errados, eles levam o otimizador a fazer más escolhas. As más escolhas no início do plano levam a escolhas ainda piores mais tarde, em um efeito de bola de neve.

Não é desconhecido que uma consulta de tamanho médio retorne pequenas quantidades de dados para levar minutos para ser executada. A indexação correta e boas estatísticas reduzem isso a milissegundos.

Michael Green
fonte
-3

Várias consultas é o caminho a percorrer. Se você lida com cenários simples como esse - o custo adicional do otimizador de consulta é um fator. Com mais dados, a ineficiência de rede da junção (linhas redundantes) entra. Somente com muito mais dados há eficiência.

No final, o que você experimenta é algo que muitos desenvolvedores veem. Os DBAs sempre dizem "não, faça uma junção", mas a realidade é: é mais rápido fazer várias seleções simples nesse caso.

TomTom
fonte
5
Não há nenhuma "ineficiência de rede" em uma junção - tudo acontece no servidor de banco de dados, por isso não há rede envolvida (a menos que você está se juntando sobre um link db!)
Chris Saxon
2
Você pode considerar se a camada de rede tem compactação ou não. O SQL * Net da Oracle faz, na medida em que os valores repetidos na mesma coluna são compactados com eficiência.
David Aldridge
3
@ TomTom, você pode ter um argumento ou não (como David Aldridge aponta, a compressão é importante), mas sua redação é confusa. "ineficiência de rede da junção" ? Realmente, conserte isso para que fique óbvio o que você quer dizer.
ypercubeᵀᴹ
@ChrisSaxon, com certeza, existe uma imagem de tabelas para um relatório "title-> base-> table-lines" e você precisa de todas as linhas para se juntar a essas três tabelas. Cada tabela possui varchars longos, então o que acontece é que para cada linha você está repetindo esses varchars longos. A camada de aplicativo precisa alocar memória para todas essas seqüências e depois agrupá-las para o seu modelo. Mais Então eu acho que é o que ele quer dizer, não há dados enviados
MIKE
@MIKE que depende das expressões que você seleciona, não da associação. E pode haver compressão de rede. No Oracle SQL Banco de Dados * remove líquidas repetido valores duplicados nicetheory.io/2018/01/11/...
Chris Saxon