Com SourceTable
registros> 15MM e registros Bad_Phrase
> 3K, a consulta a seguir leva quase 10 horas para ser executada no SQL Server 2005 SP4.
UPDATE [SourceTable]
SET
Bad_Count=
(
SELECT
COUNT(*)
FROM Bad_Phrase
WHERE
[SourceTable].Name like '%'+Bad_Phrase.PHRASE+'%'
)
Em inglês, essa consulta está contando o número de frases distintas listadas em Bad_Phrase que são uma substring do campo Name
no SourceTable
e, em seguida, colocando esse resultado no campo Bad_Count
.
Gostaria de algumas sugestões sobre como executar essa consulta consideravelmente mais rápido.
sql-server
sql-server-2005
update
subquery
Matthew Lehrer
fonte
fonte
Respostas:
Embora eu concorde com outros comentadores de que este é um problema computacionalmente caro, acho que há muito espaço para melhorias, aprimorando o SQL que você está usando. Para ilustrar, criei um conjunto de dados falso com nomes de 15MM e frases de 3K, executei a abordagem antiga e uma nova abordagem.
Script completo para gerar um conjunto de dados falso e experimentar a nova abordagem
TL; DR
Na minha máquina e neste conjunto de dados falsos, a abordagem original leva cerca de 4 horas para ser executada. A nova abordagem proposta leva cerca de 10 minutos , uma melhoria considerável. Aqui está um breve resumo da abordagem proposta:
Abordagem original: análise algorítmica
No plano da
UPDATE
declaração original , podemos ver que a quantidade de trabalho é linearmente proporcional ao número de nomes (15MM) e ao número de frases (3K). Portanto, se multiplicarmos o número de nomes e frases por 10, o tempo de execução geral será aproximadamente 100 vezes mais lento.A consulta também é proporcional ao comprimento da
name
; Embora isso esteja um pouco oculto no plano de consulta, ele aparece no "número de execuções" para procurar no spool da tabela. No plano real, podemos ver que isso ocorre não apenas uma vez porname
, mas na verdade uma vez por deslocamento de caractere dentro doname
. Portanto, essa abordagem é O (# names
*# phrases
*name length
) na complexidade do tempo de execução.Nova abordagem: código
Esse código também está disponível no pastebin completo, mas eu o copiei aqui por conveniência. O pastebin também possui a definição completa do procedimento, que inclui as variáveis
@minId
e@maxId
que você vê abaixo para definir os limites do lote atual.Nova abordagem: planos de consulta
Primeiro, geramos a substring começando em cada deslocamento de caractere
Em seguida, crie um índice em cluster nessas substrings
Agora, para cada frase ruim, procuramos nessas substrings para identificar quaisquer correspondências. Em seguida, calculamos o número de frases ruins distintas que correspondem a uma ou mais substrings dessa sequência. Este é realmente o passo chave; devido à maneira como indexamos as substrings, não precisamos mais verificar um produto cruzado completo de frases e nomes ruins. Esta etapa, que faz o cálculo real, responde por apenas cerca de 10% do tempo de execução real (o restante é o pré-processamento de substrings).
Por fim, execute a instrução de atualização real, usando a
LEFT OUTER JOIN
para atribuir uma contagem de 0 a qualquer nome para o qual não encontramos frases ruins.Nova abordagem: análise algorítmica
A nova abordagem pode ser dividida em duas fases, pré-processamento e correspondência. Vamos definir as seguintes variáveis:
N
= número de nomesB
= # de frases ruinsL
= comprimento médio do nome, em caracteresA fase de pré-processamento é
O(N*L * LOG(N*L))
para criarN*L
substrings e depois classificá-los.A correspondência real é
O(B * LOG(N*L))
para procurar nas substrings para cada frase incorreta.Dessa maneira, criamos um algoritmo que não é escalável linearmente com o número de frases ruins, um desbloqueio de desempenho importante à medida que escalamos para frases 3K e além. Dito de outra maneira, a implementação original leva aproximadamente 10x, desde que passemos de 300 frases ruins para 3K frases ruins. Da mesma forma, levaria mais 10 vezes mais tempo se passássemos de 3 mil frases ruins para 30 mil. A nova implementação, no entanto, aumentará de forma sub-linear e, na verdade, leva menos de duas vezes o tempo medido em 3 mil frases ruins quando escalado até 30 mil frases ruins.
Pressupostos / Advertências
SORT
as substrings sejam independentes para cada lote e se ajustem facilmente à memória. Você pode manipular o tamanho do lote conforme necessário, mas não seria prudente tentar todas as 15MM linhas em um lote.Post do blog relacionado
Aaron Bertrand explora esse tipo de solução com mais detalhes em sua postagem no blog. Uma maneira de obter uma pesquisa de índice para um% curinga líder .
fonte
Vamos arquivar a questão óbvia levantada por Aaron Bertrand nos comentários por um segundo:
O fato de sua subconsulta usar os curingas de ambos os lados afeta drasticamente a sargabilidade . Para fazer uma cotação dessa postagem no blog:
Troque a palavra "porca" por cada "palavra ruim" e "Produto" para
SourceTable
, em seguida, combine isso com o comentário de Aaron e comece a ver por que é extremamente difícil ( não é possível ler) fazer com que seja executado rapidamente usando o algoritmo atual.Vejo algumas opções:
Dependendo dos seus requisitos, eu recomendaria a opção 3 ou 4.
fonte
primeiro que é apenas uma atualização estranha
Como '%' + [Bad_Phrase]. [PHRASE] está matando você
Isso não pode usar um índice
O design dos dados não é ideal para velocidade
Você pode dividir a [Bad_Phrase]. [PHRASE] em uma única frase / palavras?
Se a mesma frase / palavra aparecer mais de uma, você poderá inseri-la mais de uma vez, se desejar que ela tenha uma contagem mais alta.
Portanto, o número de linhas em má posição aumentaria.
Se você puder, isso será muito mais rápido.
Não tenho certeza se 2005 o suporta, mas o Índice de texto completo e o uso Contém
fonte