J'ai une table dans SQL Server 2014 qui ressemble à ceci:
CREATE TABLE dbo.MyTable
(
[id1] [bigint] NOT NULL,
[id2] [bigint] NOT NULL,
[col1] [int] NOT NULL default(0),
[col2] [int] NOT NULL default(0)
)
avec (id1, id2) étant le PK. Fondamentalement, id1 est un identifiant pour regrouper un ensemble de résultats (id2, col1, col2), dont pk est id2.
J'essaie d'utiliser une table en mémoire pour se débarrasser d'une table sur disque existante qui est mon goulot d'étranglement.
- Les données du tableau sont écrites -> lues -> supprimées une fois.
- Chaque valeur id1 a plusieurs (dizaines / centaines de) milliers de id2.
- Les données sont stockées dans le tableau pendant très peu de temps, par exemple 20 secondes.
Les requêtes effectuées sur ce tableau sont les suivantes:
-- INSERT (can vary from 10s to 10,000s of records):
INSERT INTO MyTable
SELECT @fixedValue, id2, col1, col2 FROM AnotherTable
-- READ:
SELECT id2, col1
FROM MyTable INNER JOIN OtherTbl ON MyTable.id2 = OtherTbl.pk
WHERE id1 = @value
ORDER BY col1
-- DELETE:
DELETE FROM MyTable WHERE id1 = @value
Voici la définition actuelle que j'ai utilisée pour le tableau:
CREATE TABLE dbo.SearchItems
(
[id1] [bigint] NOT NULL,
[id2] [bigint] NOT NULL,
[col1] [int] NOT NULL default(0),
[col2] [int] NOT NULL default(0)
CONSTRAINT PK_Mem PRIMARY KEY NONCLUSTERED (id1,id2),
INDEX idx_Mem HASH (id1,id2) WITH (BUCKET_COUNT = 131072)
) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_ONLY)
Malheureusement, cette définition entraîne une dégradation des performances par rapport à la situation précédente avec une table sur disque. L'ordre de grandeur est plus ou moins 10% plus élevé (qui dans certains cas atteint 100%, donc double temps).
Surtout, je m'attendais à gagner un super-avantage dans les scénarios à forte concurrence, compte tenu de l'architecture sans verrouillage annoncée par Microsoft. Au lieu de cela, les pires performances sont exactement lorsque plusieurs utilisateurs simultanés exécutent plusieurs requêtes sur la table.
Des questions:
- quel est le BUCKET_COUNT correct à définir?
- quel type d'index dois-je utiliser?
- pourquoi les performances sont pires qu'avec la table sur disque?
Une requête de sys.dm_db_xtp_hash_index_stats renvoie:
total_bucket_count = 131072 empty_bucket_count = 0 avg_chain_len = 873 max_chain_length = 1009
J'ai changé le nombre de compartiments afin que la sortie de sys.dm_db_xtp_hash_index_stats soit:
total_bucket_count = 134217728 empty_bucket_count = 131664087 avg_chain_len = 1 max_chain_length = 3
Pourtant, les résultats sont presque les mêmes, sinon pire.
la source
OPTION(OPTIMIZE FOR UNKNOWN)
(voir les conseils de tableau )?select * from sys.dm_db_xtp_hash_index_stats
? En outre, ce lien devrait répondre à la plupart / à toutes vos questions: msdn.microsoft.com/en-us/library/…Réponses:
Bien que ce message ne soit pas une réponse complète en raison du manque d'informations, il devrait être en mesure de vous orienter dans la bonne direction ou d'obtenir des informations que vous pourrez ensuite partager avec la communauté.
C'est troublant car cela ne devrait certainement pas être le cas. Certaines charges de travail ne sont pas destinées aux tables en mémoire (SQL 2014) et certaines charges de travail s'y prêtent. Dans la plupart des situations, il peut y avoir une augmentation minimale des performances simplement en migrant et en choisissant les index appropriés.
À l'origine, je pensais très étroitement à vos questions à ce sujet:
Au début, je pensais qu'il y avait un problème avec la table et les index réels en mémoire qui n'étaient pas optimaux. Bien qu'il y ait des problèmes avec la définition d'index de hachage optimisé en mémoire, je pense que le vrai problème concerne les requêtes utilisées.
Cet insert devrait être extrêmement rapide s'il ne concernait que la table in memory. Cependant, il implique également une table sur disque et est soumis à tous les verrous et blocages associés à cela. Ainsi, le gaspillage en temps réel se trouve ici sur la table basée sur le disque.
Quand j'ai fait un test rapide contre 100 000 lignes d'insertion à partir de la table sur disque après avoir chargé les données en mémoire - c'était un temps de réponse inférieur à la seconde. Cependant, la plupart de vos données ne sont conservées que très peu de temps, moins de 20 secondes. Cela ne lui laisse pas beaucoup de temps pour vraiment vivre dans le cache. De plus, je ne suis pas sûr de la taille
AnotherTable
réelle et je ne sais pas si les valeurs sont lues sur le disque ou non. Nous devons compter sur vous pour ces réponses.Avec la requête Select:
Encore une fois, nous sommes à la merci des performances des tables basées sur disque + interop. De plus, les tris ne sont pas bon marché sur les index HASH et un index non cluster doit être utilisé. Ceci est indiqué dans le guide d'index que j'ai lié dans les commentaires.
Pour donner des faits réels basés sur la recherche, j'ai chargé la
SearchItems
table en mémoire avec 10 millions de lignes etAnotherTable
100 000 car je n'en connaissais pas la taille réelle ni les statistiques. J'ai ensuite utilisé la requête de sélection ci-dessus pour exécuter. De plus, j'ai créé une session d'événements étendue sur wait_completed et l'ai mise dans un tampon en anneau. Il a été nettoyé après chaque passage. J'ai également couruDBCC DROPCLEANBUFFERS
pour simuler un environnement où toutes les données peuvent ne pas résider en mémoire.Les résultats n'ont rien de spectaculaire quand on les regarde dans le vide. Étant donné que l'ordinateur portable sur lequel je teste cela utilise un SSD de qualité supérieure, j'ai artificiellement réduit les performances basées sur le disque pour la machine virtuelle que j'utilise.
Les résultats sont arrivés sans aucune information d'attente après 5 exécutions de la requête uniquement sur la table en mémoire (suppression de la jointure et pas de sous-requêtes). C'est à peu près comme prévu.
Cependant, lors de l'utilisation de la requête d'origine, j'ai dû attendre. Dans ce cas, c'est PAGEIOLATCH_SH qui a du sens lorsque les données sont lues sur le disque. Étant donné que je suis le seul utilisateur de ce système et que je n'ai pas passé de temps à créer un environnement de test massif pour les insertions, les mises à jour et les suppressions par rapport à la table jointe, je ne m'attendais pas à ce qu'un verrouillage ou un blocage entre en vigueur.
Dans ce cas, encore une fois, la partie importante du temps a été consacrée à la table sur disque.
Enfin la requête de suppression. Trouver les lignes basées uniquement sur ID1 n'est pas extrêmement efficace avec un index has. S'il est vrai que les prédicats d'égalité sont les indices de hachage appropriés, le compartiment dans lequel les données tombent est basé sur l'ensemble des colonnes hachées. Ainsi, id1, id2 où id1 = 1, id2 = 2 et id1 = 1, id2 = 3 seront hachés dans des compartiments différents car le hachage sera à travers (1,2) et (1,3). Ce ne sera pas un simple scan de plage B-Tree car les index de hachage ne sont pas structurés de la même manière. Je m'attendrais alors à ce que ce ne soit pas l' indice idéal pour cette opération, mais je ne m'attendrais pas à ce qu'il prenne des ordres de grandeur plus longtemps que l'expérience. Je serais intéressé de voir le wait_info à ce sujet.
S'il est vrai que les verrous sont utilisés pour la cohérence logique, les opérations doivent toujours être atomiques. Cela se fait via un opérateur de comparaison basé sur le processeur spécial (c'est pourquoi In-Memory ne fonctionne qu'avec certains processeurs [quoique presque tous les processeurs fabriqués au cours des 4 dernières années]). Ainsi, nous ne recevons pas tout gratuitement, il nous restera encore du temps pour terminer ces opérations.
Un autre point à évoquer est le fait que dans presque toutes les requêtes, l'interface utilisée est T-SQL (et non les SPROC compilés nativement) qui touchent tous au moins une table sur disque. C'est pourquoi je pense qu'en fin de compte, nous n'avons en fait aucune augmentation des performances car nous sommes toujours limités aux performances des tables sur disque.
Suivre:
Créez une session d'événement étendue pour wait_completed et spécifiez un SPID connu de vous. Exécutez la requête et donnez-nous la sortie ou consommez-la en interne.
Donnez-nous une mise à jour sur la sortie de # 1.
Il n'y a pas de nombre magique pour déterminer le nombre de compartiments pour les index de hachage. Fondamentalement, tant que les godets ne sont pas complètement remplis et que les chaînes de rangées restent en dessous de 3 ou 4, les performances doivent rester acceptables. C'est un peu comme demander: "À quoi dois-je définir mon fichier journal?" - cela va dépendre par processus, par base de données, par type d'utilisation.
la source