Avec SourceTable
> 15MM d'enregistrements et Bad_Phrase
ayant> 3K enregistrements, la requête suivante prend près de 10 heures pour s'exécuter sur SQL Server 2005 SP4.
UPDATE [SourceTable]
SET
Bad_Count=
(
SELECT
COUNT(*)
FROM Bad_Phrase
WHERE
[SourceTable].Name like '%'+Bad_Phrase.PHRASE+'%'
)
En anglais, cette requête compte le nombre d'expressions distinctes répertoriées dans Bad_Phrase qui sont une sous-chaîne du champ Name
dans le SourceTable
puis en plaçant ce résultat dans le champ Bad_Count
.
Je voudrais quelques suggestions sur la façon d'exécuter cette requête beaucoup plus rapidement.
sql-server
sql-server-2005
update
subquery
Matthew Lehrer
la source
la source
Réponses:
Bien que je convienne avec d'autres commentateurs qu'il s'agit d'un problème de calcul coûteux, je pense qu'il y a beaucoup de place à l'amélioration en peaufinant le SQL que vous utilisez. Pour illustrer, je crée un faux ensemble de données avec des noms 15MM et des phrases 3K, j'ai exécuté l'ancienne approche et j'ai exécuté une nouvelle approche.
Script complet pour générer un faux ensemble de données et essayer la nouvelle approche
TL; DR
Sur ma machine et ce faux ensemble de données, l' approche originale prend environ 4 heures pour s'exécuter. La nouvelle approche proposée prend environ 10 minutes , une amélioration considérable. Voici un bref résumé de l'approche proposée:
Approche originale: analyse algorithmique
D'après le plan de l'original
UPDATE
déclaration d' , nous pouvons voir que la quantité de travail est linéairement proportionnelle à la fois au nombre de noms (15MM) et au nombre de phrases (3K). Donc, si nous multiplions le nombre de noms et de phrases par 10, le temps d'exécution global sera ~ 100 fois plus lent.La requête est en fait proportionnelle à la longueur de la
name
aussi; bien que cela soit un peu caché dans le plan de requête, il apparaît dans le "nombre d'exécutions" pour rechercher dans le spouleur de table. Dans le plan réel, nous pouvons voir que cela se produit non seulement une fois parname
, mais en fait une fois par caractère décalé dans lename
. Cette approche est donc O (# names
*# phrases
*name length
) en complexité d'exécution.Nouvelle approche: code
Ce code est également disponible dans le casier complet mais je l'ai copié ici pour plus de commodité. Le pastebin a également la définition de procédure complète, qui comprend les variables
@minId
et@maxId
que vous voyez ci-dessous pour définir les limites du lot en cours.Nouvelle approche: plans de requête
Tout d'abord, nous générons la sous-chaîne à partir de chaque décalage de caractère
Créez ensuite un index cluster sur ces sous-chaînes
Maintenant, pour chaque mauvaise phrase, nous recherchons dans ces sous-chaînes pour identifier les correspondances. Nous calculons ensuite le nombre de mauvaises phrases distinctes qui correspondent à une ou plusieurs sous-chaînes de cette chaîne. C'est vraiment l'étape clé; en raison de la façon dont nous avons indexé les sous-chaînes, nous n'avons plus à vérifier un produit croisé complet de mauvaises phrases et de mauvais noms. Cette étape, qui effectue le calcul réel, ne représente qu'environ 10% du temps d'exécution réel (le reste est le prétraitement des sous-chaînes).
Enfin, effectuez l'instruction de mise à jour réelle, en utilisant a
LEFT OUTER JOIN
pour attribuer un nombre de 0 à tous les noms pour lesquels nous n'avons trouvé aucune mauvaise phrase.Nouvelle approche: analyse algorithmique
La nouvelle approche peut être divisée en deux phases, le prétraitement et l'appariement. Définissons les variables suivantes:
N
= nombre de nomsB
= nombre de mauvaises phrasesL
= longueur moyenne du nom, en caractèresLa phase de prétraitement consiste
O(N*L * LOG(N*L))
à créer desN*L
sous-chaînes puis à les trier.La correspondance réelle vise
O(B * LOG(N*L))
à rechercher dans les sous-chaînes pour chaque mauvaise phrase.De cette façon, nous avons créé un algorithme qui n'évolue pas linéairement avec le nombre de phrases erronées, un déverrouillage des performances clés lorsque nous évoluons vers des expressions 3K et au-delà. Autrement dit, l'implémentation d'origine prend environ 10 fois aussi longtemps que nous passons de 300 phrases mauvaises à 3K phrases mauvaises. De même, cela prendrait 10 fois plus de temps si nous passions de 3K mauvaises phrases à 30K. La nouvelle implémentation, cependant, évoluera de manière sous-linéaire et prend en fait moins de 2x le temps mesuré sur les 3K mauvaises phrases lorsqu'elle est mise à l'échelle jusqu'à 30K de mauvaises phrases.
Hypothèses / mises en garde
SORT
les sous-chaînes soient indépendantes pour chaque lot et tiennent facilement en mémoire. Vous pouvez manipuler la taille du lot selon vos besoins, mais il ne serait pas judicieux d'essayer toutes les lignes de 15 mm en un seul lot.Article de blog connexe
Aaron Bertrand explore ce type de solution plus en détail dans son article de blog One way pour obtenir une recherche d'index pour un% générique de premier plan .
la source
Laissons de côté le problème évident soulevé par Aaron Bertrand dans les commentaires pendant une seconde:
Le fait que votre sous-requête utilise les caractères génériques des deux côtés affecte considérablement la sargabilité . Pour prendre une citation de ce billet de blog:
Échangez le mot «écrou» pour chaque «mauvais mot» et «produit» pour
SourceTable
, puis combinez cela avec le commentaire d'Aaron et vous devriez commencer à voir pourquoi il est extrêmement difficile (lire impossible) de le faire fonctionner rapidement en utilisant votre algorithme actuel.Je vois quelques options:
Selon vos besoins, je recommanderais l'option 3 ou 4.
la source
c'est juste une étrange mise à jour
Comme '%' + [Bad_Phrase]. [PHRASE] vous tue
Qui ne peut pas utiliser d'index
La conception des données n'est pas optimale pour la vitesse
Pouvez-vous diviser la [Bad_Phrase]. [PHRASE] en une seule phrase (s) / mot?
Si la même phrase / mot apparaît plus que vous pouvez entrer plus d'une fois si vous voulez avoir un nombre plus élevé
Ainsi , le nombre de lignes en mauvais pharase grimperait
Si vous pouvez alors ce sera beaucoup plus rapide
Je ne sais pas si 2005 le prend en charge, mais l'index de texte intégral et l'utilisation contiennent
la source