O que é mais eficiente, uma cláusula where ou uma junção com milhões de tabelas de linhas?

17

Executamos um site com 250 MM de linhas em uma tabela e em outra tabela à qual o associamos à maioria das consultas tem pouco menos de 15 MM de linhas.

Estruturas de amostra:

MasterTable (Id, UserId, Created, Updated...) -- 15MM Rows
DetailsTable (Id, MasterId, SomeColumn...) -- 250MM Rows
UserTable (Id, Role, Created, UserName...) -- 12K Rows

Regularmente, precisamos fazer algumas consultas em todas essas tabelas. Um deles é pegar estatísticas para usuários gratuitos (~ 10.000 usuários gratuitos).

Select Count(1) from DetailsTable dt 
join MasterTable mt on mt.Id = dt.MasterId 
join UserTable ut on ut.Id = mt.UserId 
where ut.Role is null and mt.created between @date1 and @date2

O problema é que essa consulta algumas vezes demora muito, devido ao fato de as junções acontecerem muito antes do local.

Nesse caso, seria mais sensato usar wherees em vez de junções ou possivelmente where column in(...)?

Jeremy Boyd
fonte
11
Qual banco de dados e versão?
Leigh Riffel
11
você já tentou nos dois sentidos?
gbn 24/06
Se esse fosse o Oracle, eu criaria um índice baseado em função para a UserTable no NVL2 (função, NULL, ID), mas isso se parece com outro banco de dados.
Leigh Riffel

Respostas:

20

Para RDBMS moderno, não há diferença entre "JOIN explícito" e "JOIN-in-the-WHERE" (se todas as JOINS forem INNER) em relação ao desempenho e ao plano de consulta.

A sintaxe JOIN explícita é mais clara e menos ambígua (veja os links abaixo)

Agora, o JOIN-before-WHERE é um processamento lógico , não um processamento real , e os otimizadores modernos são espertos o suficiente para realizar isso.

Seu problema aqui é provavelmente a indexação.

Por favor, mostre-nos todos os índices e chaves nessas tabelas. E os planos de consulta

Nota: essa questão já estaria próxima no StackOverflow por ser uma duplicata agora ... COUNT (1) vs COUNT (*) também é outro mito eliminado.

gbn
fonte
2
Nem sempre é verdade que não há diferença entre joine wherecláusula. Otimizo as consultas de longa duração o tempo todo e, às vezes, a consulta usando a wherecláusula tem melhor desempenho do que as que são usadas joinpor um fator de até 70x. Se fosse assim tão simples e direta, a vida seria todos arco-íris e unicórnios. E não se trata de um antigo motor obscuro - agora eu estou olhando para 70x vantagem de wherecláusula no SQL 2012.
ajeh
Além disso, muitas vezes observo exatamente os mesmos planos de ambas as abordagens e isolamos as consultas exatamente da mesma maneira, mas quando a whereconsulta de cláusula é executada no lote grande do qual ele deve fazer parte, ela supera a joinconsulta por uma margem enorme. As consultas SQL não são executadas no vácuo - elas são afetadas pelo restante da carga útil do servidor e, muitas vezes, as whereconsultas da cláusula se saem muito bem, o que é um aborrecimento, pois a joinsintaxe é realmente muito mais limpa.
ajeh
3
@ ajeh: eu sugeriria que sua experiência é muito atípica. Você tem maiores problemas com consultas se você tiver X70 diferenças: é tão simples
GBN
5

Você deve refatorar completamente a consulta

Tente executar as cláusulas WHERE mais cedo e os JOINs depois

Select Count(1) from DetailsTable dt
join (Select UserId,Id FROM MasterTable where
created between @date1 and @date2) mt on mt.Id = dt.MasterId 
join (Select Id FROM UserTable WHERE Role is NULL) ut
on ut.Id = mt.UserId;

Mesmo se você executar um plano EXPLAIN nessa consulta refatorada e parecer pior que o original, tente mesmo assim. As tabelas temporárias criadas internamente executarão junções cartesianas, mas essas tabelas são menores para trabalhar.

Eu recebi essa ideia deste vídeo do YouTube .

Experimentei os princípios do vídeo em uma pergunta muito complexa no StackOverflow e recebi uma recompensa de 200 pontos.

@gbn mencionado, certificando-se de que você tenha os índices corretos. Nesse caso, indexe a coluna criada no MasterTable.

De uma chance !!!

UPDATE 2011-06-24 22:31 EDT

Você deve executar estas consultas:

SELECT COUNT(1) AllRoles FROM UserTable;
SELECT COUNT(1) NullRoles FROM UserTable WHERE Role is NULL;

Se NullRoles X 20 <AllRoles (em outras palavras, se NullRoles for menor que 5% das linhas da tabela), você deverá criar um índice não exclusivo da função na UserTable. Caso contrário, uma tabela completa da UserTable seria suficiente, pois o Query Optimizer pode possivelmente descartar o uso de um índice.

ATUALIZAÇÃO 25-06-2011 12:40 EDT

Como sou um DBA do MySQL, meu método de fazer as coisas requer não confiar no MySQL Query Optimizer através de pessimismo positivo e ser conservador. Assim, tentarei refatorar uma consulta ou criar índices de cobertura necessários para superar os maus hábitos ocultos do MySQL Query Optimizer. A resposta da @ gbn parece mais completa, pois o SQL Server pode ter mais "senso de humor" na avaliação de consultas.

RolandoMySQLDBA
fonte
0

Tínhamos uma tabela [Detalhe] com cerca de 75 milhões de linhas; uma tabela [Master] com cerca de 400 K linhas e uma tabela [Item] relacionada com 7 linhas - sempre e para sempre. Ele armazenava o pequeno conjunto de “Números de itens” (1-7) e modelava um formulário em papel, milhões dos quais eram impressos e distribuídos todos os meses. A consulta mais rápida foi a que você provavelmente menos pensou primeiro, envolvendo o uso de uma junção cartesiana. IIRC, era algo como:

SELECT m.order_id, i.line_nr, d.Item_amt
FROM Master m, Item i 
INNER JOIN Detail d ON m.order_id = d.order_id

Embora exista um vínculo lógico de "id" entre [Item] e [Detalhe], o CROSS JOIN funcionou melhor que o INNER JOIN.

O RDBMS foi o Teradata com sua tecnologia MPP e o IDR, como era o esquema de indexação. A tabela de 7 linhas não tinha índice, pois a TABLE SCAN sempre apresentava o melhor desempenho.

Timothy Oleary
fonte