6
votes

Optimiser Self Rejoignez sur des millions de lignes

J'ai une table qui est une table de liaison des objets de ma base de données SQL Server 2012 ( annonsid, annonsid2 code>). Cette table est utilisée pour créer des chaînes de triangle ou même des rectangles pour voir qui peut échanger avec qui.

Ceci est la requête que j'utilise sur la table correspondant_ids code> qui compte 1,5 million de lignes dedans, Produire 14 millions de chaînes possibles à l'aide de cette requête: P>

|--Compute Scalar(DEFINE:([Expr1006]=CONVERT_IMPLICIT(int,[globalagg1011],0)))
   |--Stream Aggregate(DEFINE:([globalagg1011]=SUM([partialagg1010])))
        |--Parallelism(Gather Streams)
             |--Stream Aggregate(DEFINE:([partialagg1010]=Count(*)))
                  |--Hash Match(Inner Join, HASH:([m2].[annonsid2], [m2].[annonsid])=([m3].[annonsid], [m].[annonsid2]), RESIDUAL:([MyDatabase].[dbo].[Matching_IDs].[annonsid2] as [m2].[annonsid2]=[MyDatabase].[dbo].[Matching_IDs].[annonsid] as [m3].[annonsid] AND [MyDatabase].[dbo].[Matching_IDs].[annonsid2] as [m].[annonsid2]=[MyDatabase].[dbo].[Matching_IDs].[annonsid] as [m2].[annonsid]))
                       |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([m2].[annonsid2], [m2].[annonsid]))
                       |    |--Index Scan(OBJECT:([MyDatabase].[dbo].[Matching_IDs].[NonClusteredIndex-20121229-133207] AS [m2]))
                       |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([m3].[annonsid], [m].[annonsid2]))
                            |--Merge Join(Inner Join, MANY-TO-MANY MERGE:([m].[annonsid])=([m3].[annonsid2]), RESIDUAL:([MyDatabase].[dbo].[Matching_IDs].[annonsid] as [m].[annonsid]=[MyDatabase].[dbo].[Matching_IDs].[annonsid2] as [m3].[annonsid2]))
                                 |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([m].[annonsid]), ORDER BY:([m].[annonsid] ASC))
                                 |    |--Index Scan(OBJECT:([MyDatabase].[dbo].[Matching_IDs].[NonClusteredIndex-20121229-133152] AS [m]), ORDERED FORWARD)
                                 |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([m3].[annonsid2]), ORDER BY:([m3].[annonsid2] ASC))
                                      |--Index Scan(OBJECT:([MyDatabase].[dbo].[Matching_IDs].[NonClusteredIndex-20121229-133207] AS [m3]), ORDERED FORWARD)


20 commentaires

Quels index avez-vous définis sur la table? Un index (annonsid2, annonsid) peut être une bonne option.


J'ai un index en cluster des deux colonnes et une non-regroupée pour chaque colonne (2)


Où le plan de requête dit-il que le temps est pris?


Eh bien, il faut environ 2 secondes après que je appuie sur couru jusqu'à ce qu'il commence à afficher les résultats, mais cela ne signifie pas que la requête est si rapide ?, Parce quand je viens de faire un compte (m.Annonsid) au lieu d'obtenir tout le Records, sa prenant environ 1 minute.


Pourriez-vous ajouter un plan de requête à la question?


Sur lequel les deux? Avez-vous composite pk?


Voici une capture d'écran de mon plan d'exécution: lien


Ceci est un plan d'exécution estimé. Utilisez SET WORSPLAN_TEXT sur pour obtenir le plan d'exécution et publier ici.


Ok, exécutez-le maintenant, en train de poster une fois terminé. Merci!


Plan d'exécution exécuté (1 minute), lien


Rien de nouveau. Avez-vous défini l'option Afficher le plan de texte? Et dans votre diagramme de plan estimé, je n'ai pas vu où la clause?


Ok désolé, un peu nouveau à cela, il vient ici: lien


Le test de requête ne correspond pas au plan d'exécution. Veuillez les rendre cohérents.


Il fait maintenant, c'était juste compter (*) au lieu de sélectionner des colonnes.


just for a try SELECT COUNT(*) FROM Matching_IDs AS m INNER JOIN Matching_IDs AS m2 ON m.annonsid2 = m2.annonsid Where Exist(Select * from Matching_IDs m3 where m2.annonsid2 = m3.annonsid AND m.annonsid = m3.annonsid2 )


Que signifie "qui peut échanger avec qui" signifie? Pourriez-vous fournir des échantillons de données s'il vous plaît et décrivez ce que cela signifie? Lorsque vous ne faites pas que faire comptez (*) , quelles colonnes tirez-vous? Sans vraiment comprendre le contexte complet de ce que vous faites, notre cerveau ne peut pas activer efficacement pour être sûr que nous vous donnons la meilleure réponse. Parfois, des réponses que vous n'auriez pas pu imaginer sont possibles, mais uniquement lorsque les experts sont pleinement conscients de l'espace problématique plutôt que de la petite tranche que vous avez présentée.


Erik e, je comprends. Ce que je suis est une table avec 2 identifiants (ID1, ID2), il s'agit d'un identifiant d'une annonce et de points que ID1 est intéressé par l'échange avec ID2. De cette façon, je peux construire des swaps directs entre deux annonces (2 IDS), des swaps triangle comme un triangle avec trois coins où ID1 obtient ID2, qui obtient ID3 qui obtient ID1. Idem dans un swap rectangle, ID1> ID2> ID3> ID4> ID1. Comprenez vous?


Veuillez étiqueter avec le symbole @ ou les gens ne seront pas notifiés. Pourquoi l'échange est-il toujours en avant - pour chaque identifiant ID2 est ID2, ID dans la table? Que faites-vous avec les résultats? Si vous avez des annonces 1, 2, 3 et 4, vous avez des lignes 1,2 , 2,3 , 3,4 et 4,1 (non?) Mais pourquoi pas 2,4 ou 4,2 ? Quelque chose ici déclenche mon instinct qu'il pourrait y avoir un moyen différent d'accomplir votre but.


@ Infinity1975 Je souhaite que vous répondiez, cela ressemble à un puzzle amusant / intéressant.


1,5 million n'est rien. Nous avons besoin d'un nombre que Soi se joint à une table de 40 millions de rangées (et de comptage). Évidemment, ça ne travaille pas trop bien. : /


5 Réponses :


1
votes

On dirait que vous êtes déjà indexé cela très bien. Vous pouvez essayer de convertir le hachage en une jointure de fusion en ajoutant le bon index multi-colonnes, mais cela ne vous donnera pas la vitesse souhaitée de 60x.

Je pense que cet index serait sur annonsid, annonsid2 bien que j'ai pu faire une erreur ici.

Il serait agréable de concrétiser tout cela mais les vues indexées ne prennent pas en charge les joints de soi. Vous pouvez essayer de matérialiser cette requête (non grise) dans une nouvelle table. Chaque fois que vous exécutez DML contre la table de base, mettez également à jour la deuxième table (à l'aide de la logique d'application ou des déclencheurs). Cela vous permettrait d'interroger une flamboyante rapide.


0 commentaires

1
votes

Vous devez rendre cette requête un peu plus séparée. Je pense d'abord que vous devriez créer une table, où vous pouvez stocker la clé primaire + annonside, annonsid2 - si l'annosid n'est pas la clé principale elle-même . xxx

Si vous déclarez une table et que vous avez l'index sur cette colonne, il est assez rapide d'obtenir les lignes spécifiées par le où annonsid = @nononid ou annonsid2 = @Annosid < / Code>

Après la première étape, vous avez un peu plus petit (je suppose), et une table "mince" pour travailler avec. Ensuite, il vous suffit d'utiliser les jointures ici ou de faire une table Temp et un CTE dessus.

Je pense que cela devrait être plus rapide, en fonction de la sélectivité de la condition dans le , de vous si 1,1 million de lignes s'adapte, il n'a pas de sens, mais Si seulement quelques centaines de centaines ou t'osend, alors vous devriez essayer!


0 commentaires

3
votes

Quelques idées:

Essayez deux index (annonsid, annonsid2) et (annonsid2, annonsid) p>

Avez-vous essayé d'un index de stockage de colonne? Il ne fait que lire la table, mais cela pourrait améliorer les performances. P>

En outre, certaines variations de la requête pouvaient aider. Exemples: P>

SELECT COUNT(*)
FROM Matching_IDs AS m
  INNER JOIN Matching_IDs AS m2
     ON m.annonsid2 = m2.annonsid


5 commentaires

# 1 et n ° 2 est Similair (22 secondes) identique à ma requête normale, j'ai réussi à utiliser une table distincte qui descendit de 1 minute à 22 secondes. La 3ème requête est très rapide, 2 secondes. J'ai 8 Go de RAM.


En fait, ce que je veux vraiment, c'est de supprimer la table d'identité de 1 à 1, mais je n'ai aucune idée de la façon dont je peux savoir quelle annonce correspond à une manière facile. Je pensais avoir beaucoup de Bigint's et d'utiliser des bits et entre les annonces. Cela réduit la recherche jusqu'à 15 000 annonces (rangées) au lieu de 1,5 million. Mais, le résultat final sera de toute façon 15 millions de personnes afin que cela ne valait pas la peine.


Que voulez-vous dire par 1 à 1 table d'identité? En ce qui concerne la performance: je viens de faire un test sur mon ordinateur portable. Compter 6 Mio Rows prend un moment (20 secondes) et donc je doute que vous puissiez obtenir beaucoup plus vite.


C'est une table avec 2 id poinçants de gauche à droite, ID1 veut ID2.


Que diriez-vous d'ajouter et m.annonid! = M.Annonid2 ? Faites cela avec les trois tables (M, M1, M2). Cela pourrait aider un peu, mais ils sont sortis de toute façon, comme vous l'avez dit.



0
votes

Vous pouvez désormaliser les données en ajoutant une table liaisids code> avec annonsid code>, likaannonid code> et distance code>. Pour chaque valeur de annonsid code>, la table contient des lignes pour chaque likaannonid code> et le nombre de relations à parcourir pour l'atteindre, aka distance code> . Les déclencheurs sur la table code> MatchingIDs existant code> conserveraient la nouvelle table avec une valeur maximale configurée pour Distance CODE>, E.G. 3 pour gérer les actions rectangulaires. Indexez la table sur ( annonsid code>, distance code>).

edit: em> un index sur ( distance code> Code> annonsid code>) vous permettra de trouver rapidement des lignes qui ont suffisamment d'entrées connexes pour former une forme particulière. Ajout d'une colonne pour maxdistance code> peut être utile si vous souhaitez être capable d'exclure les lignes en fonction de la relation triangulaire mais pas em> ni de relation rectangulaire. P> La nouvelle requête serait Joindre interne reliefs en tant que RI sur Ri.AnnonSid = m.AnnonSID et RI.DISTANCE avec la "forme" souhaitée dictant la valeur de @maxdistance .


doit fournir une meilleure performance sur le Sélectionner code>. Les inconvénients sont une autre table avec un grand nombre de lignes et des frais généraux des déclencheurs lors de la modification de la table Code> Matchingids Code>. P>

Exemple: Il existe deux entrées dans correspondant_ids code> : (1,2) et (2,3). La nouvelle table contiendrait 3 entrées:
1-> 2: Distance = 1
1-> 3: Distance = 2 (il faut un "nœud" intermédiaire pour aller de 1 à 3)
2-> 3: Distance = 1 P>

Ajout d'une autre entrée à l'ID correspondant (3,1) entraînerait une autre entrée:
1-> 1: Distance = 3 P>

et Voilá: Vous avez trouvé un triangle (distance = 3). P>

Maintenant, pour trouver tous les triangles, faites-le simplement: P >

select * 
  from RelatedIds 
 where AnnonsId=RelatedAnnonId 
   and Distance=3


8 commentaires

C'était un moyen de complexe pour moi de comprendre: D, ce que j'ai déjà est une table liée, c'est la table "correspondant_ids" qui ont (annonsid, annonsid2), ce que je ne comprends pas est la "distance" et comment l'obtenir ? Et ce que les déclencheurs feront.


Le problème persiste, je pense que le problème est la performance, et non de créer le résultat. Ce déclencheur n'est que la même chose que d'utiliser ma requête en fournissant une carte d'identité qui ne fonctionne que sur cet identifiant, puis enregistrez le résultat dans une deuxième table. Ai-je tort?


@ Infinity1975 - La différence est que la gâchette divise la levée lourde en une quantité relativement petite de travail chaque fois que la table de base est modifiée. Un inconvénient est que le travail est toujours effectué, même si vous n'échouez jamais pour "formes". L'avantage est que la requête «formes» devrait être beaucoup plus rapide.


Oui, j'ai une solution pour cela, sauf le paramètre de distance. J'ai une table pour directwaps et un pour Triangleswaps.


J'ai une table pour directwaps et un pour Triangleswaps. Mais j'étais sorti pour une solution faisant la première sélection plus rapide. C'est toujours une tâche fastidieuse.


@ Infinity1975 - Je continue à avoir le sentiment que vous ne comprenez pas les déclencheurs. L'idée de base est très utile de maintenir un solde de compte bancaire. Vous pouvez résumer chaque transaction pour un compte à partir du début du temps pour déterminer le solde actuel ou vous pouvez prendre un peu de temps supplémentaire lorsque chaque transaction se produit pour maintenir un champ distinct de «balance actuelle» pour le compte. La première approche est joliment normalisée, mais la performance souffrira au fil du temps. La deuxième approche diffuse la charge de travail au fil du temps afin que la performance d'interrogation d'un équilibre reste constante.


@ Infinity1975 - Votre commentaire Re: Avoir des tables pour les swaps directs et triangle n'est pas clair pour moi. Comment conservez-vous ces tables actuelles?


En les "précalcing" quand une annonce change. Sa semblable à la gâchette mais je le fais dans mon code C #.



0
votes

1 - Changer de sélectionner comptez (*) à comptez (1) ou compte (id)

2 - écrire Set Nocount sur à la première de votre procédure stockée ou à la première de votre requête

3 - Utilisez l'index sur annonsid , annonsid2

4 - Avoir vos index après la clé primaire de votre table


2 commentaires

1- Il n'y a pas de différence entre Nombre (*) et compter (1) `` `` `` Je l'ai testé.


Correct. Il y a une différence quand il s'agit de quoi compter. Mais cela ne s'applique pas au problème de Infinity1975, autant que je puisse le voir. Mais +1 pour pointer cela.