J'ai un tableau assez grand avec l'une des colonnes étant des données XML avec une taille moyenne d'entrée XML de ~ 15 kilo-octets. Toutes les autres colonnes sont des entiers réguliers, des bigints, des GUID, etc. Pour avoir des chiffres concrets, disons que la table a un million de lignes et fait environ 15 Go.
Ce que j'ai remarqué, c'est que ce tableau est vraiment lent à sélectionner des données si je veux sélectionner toutes les colonnes. Quand je fais
SELECT TOP 1000 * FROM TABLE
il faut environ 20-25 secondes pour lire les données à partir du disque - même si je n'impose aucun ordre sur le résultat. J'exécute la requête avec le cache froid (c'est-à-dire après DBCC DROPCLEANBUFFERS
). Voici les résultats des statistiques d'E / S:
Nombre de balayages 1, lectures logiques 364, lectures physiques 24, lectures anticipées 7191, lectures logiques lob 7924, lectures physiques lob 1690, lectures anticipées lob 3968.
Il capture ~ 15 Mo de données. Le plan d'exécution montre l'analyse d'index en cluster comme je m'y attendais.
Il n'y a pas d'E / S en cours sur le disque à part mes requêtes; J'ai également vérifié que la fragmentation des index cluster est proche de 0%. Il s'agit d'un disque SATA grand public, mais je pense toujours que SQL Server serait en mesure d'analyser la table plus rapidement que ~ 100-150 Mo / min.
La présence du champ XML fait que la plupart des données de table se trouvent sur les pages LOB_DATA (en fait, ~ 90% des pages de table sont LOB_DATA).
Je suppose que ma question est - ai-je raison de penser que les pages LOB_DATA peuvent provoquer des analyses lentes non seulement en raison de leur taille, mais aussi parce que SQL Server ne peut pas analyser efficacement l'index cluster lorsqu'il y a beaucoup de pages LOB_DATA dans la table?
Plus largement encore - est-il jugé raisonnable d'avoir une telle structure de tableau / modèle de données? Les recommandations d'utilisation de Filestream indiquent généralement des tailles de champ beaucoup plus grandes, donc je ne veux pas vraiment suivre cette voie. Je n'ai pas vraiment trouvé de bonnes informations sur ce scénario particulier.
J'ai pensé à la compression XML, mais elle doit être effectuée sur le client ou avec SQLCLR et nécessiterait un certain travail pour être implémentée dans le système.
J'ai essayé la compression, et comme les XML sont très redondants, je peux (dans l'application ac #) compresser le XML de 20 Ko à ~ 2,5 Ko et le stocker dans la colonne VARBINARY, empêchant l'utilisation des pages de données LOB. Cela accélère les sélections 20 fois dans mes tests.
la source
SELECT *
n'est pas le problème si vous avez besoin des données XML. Ce n'est un problème que si vous ne voulez pas les données XML, dans ce cas, pourquoi ralentir la requête pour récupérer les données que vous n'utilisez pas? J'ai posé des questions sur les mises à jour du XML en me demandant si la fragmentation sur les pages LOB n'était pas signalée avec précision. C'est pourquoi j'avais demandé dans ma réponse comment exactement avez-vous déterminé que l'index cluster n'était pas fragmenté? Pouvez-vous fournir la commande que vous avez exécutée? Et avez-vous fait une RECONSTRUCTION complète sur l'Index Clustered? (suite)Réponses:
Le simple fait d'avoir la colonne XML dans le tableau n'a pas cet effet. C'est la présence de données XML qui, dans certaines conditions , entraîne le stockage d'une partie des données d'une ligne hors ligne, sur les pages LOB_DATA. Et même si un (ou peut-être plusieurs ;-) pourrait faire valoir que duh, la
XML
colonne implique qu'il y aura effectivement des données XML, il n'est pas garanti que les données XML devront être stockées hors ligne: à moins que la ligne soit à peu près déjà remplie en dehors de leur caractère XML, les petits documents (jusqu'à 8 000 octets) peuvent tenir en ligne et ne jamais aller sur une page LOB_DATA.La numérisation consiste à regarder toutes les lignes. Bien sûr, lorsqu'une page de données est lue, toutes les données en ligne sont lues, même si vous avez sélectionné un sous-ensemble des colonnes. La différence avec les données LOB est que si vous ne sélectionnez pas cette colonne, les données hors ligne ne seront pas lues. Par conséquent, il n'est pas vraiment juste de tirer une conclusion sur l'efficacité avec laquelle SQL Server peut analyser cet index clusterisé, car vous n'avez pas exactement testé cela (ou vous en avez testé la moitié). Vous avez sélectionné toutes les colonnes, ce qui inclut la colonne XML, et comme vous l'avez mentionné, c'est là que se trouvent la plupart des données.
Nous savons donc déjà que le
SELECT TOP 1000 *
test ne consistait pas simplement à lire une série de pages de données de 8k, toutes d'affilée, mais plutôt à sauter vers d'autres emplacements pour chaque ligne . La structure exacte de ces données LOB peut varier en fonction de leur taille. Sur la base des recherches présentées ici ( Quelle est la taille du pointeur LOB pour les types (MAX) comme Varchar, Varbinary, Etc? ), Il existe deux types d'allocations LOB hors ligne:L'une de ces deux situations se produit chaque fois que vous récupérez des données LOB qui dépassent 8 000 octets ou qui ne tiennent tout simplement pas en ligne. J'ai posté un script de test sur PasteBin.com (script T-SQL pour tester les allocations et les lectures LOB ) qui montre les 3 types d'allocations LOB (en fonction de la taille des données) ainsi que l'effet de chacune d'elles sur la logique et lectures physiques. Dans votre cas, si les données XML sont réellement inférieures à 42 000 octets par ligne, aucune d'entre elles (ou très peu) ne devrait se trouver dans la structure TEXT_TREE la moins efficace.
Si vous souhaitez tester la vitesse à laquelle SQL Server peut analyser cet index clusterisé, procédez comme suit,
SELECT TOP 1000
mais spécifiez une ou plusieurs colonnes n'incluant pas cette colonne XML. Comment cela affecte-t-il vos résultats? Cela devrait être un peu plus rapide.Étant donné que nous avons une description incomplète de la structure réelle du tableau et du modèle de données, toute réponse peut ne pas être optimale en fonction de ces détails manquants. Dans cet esprit, je dirais qu'il n'y a rien de manifestement déraisonnable dans la structure de votre table ou le modèle de données.
Cela a rendu la sélection de toutes les colonnes, ou même juste les données XML (maintenant
VARBINARY
) plus rapide, mais cela bloque en fait les requêtes qui ne sélectionnent pas les données "XML". En supposant que vous avez environ 50 octets dans les autres colonnes et que vous en avezFILLFACTOR
100, alors:Aucune compression: 15k de
XML
données devraient nécessiter 2 pages LOB_DATA, ce qui nécessite alors 2 pointeurs pour la racine en ligne. Le premier pointeur fait 24 octets et le second 12, pour un total de 36 octets stockés en ligne pour les données XML. La taille totale des lignes est de 86 octets, et vous pouvez adapter environ 93 de ces lignes sur une page de données de 8060 octets. Par conséquent, 1 million de lignes nécessite 10 753 pages de données.Compression personnalisée: 2,5 k de
VARBINARY
données s'adapteront en ligne. La taille totale des lignes est de 2610 (2,5 * 1024 = 2560) octets, et vous ne pouvez insérer que 3 de ces lignes sur une page de données de 8060 octets. Par conséquent, 1 million de lignes nécessite 333 334 pages de données.Ergo, l'implémentation d'une compression personnalisée entraîne une augmentation de 30 fois le nombre de pages de données pour l'index clusterisé. Autrement dit, toutes les requêtes utilisant une analyse d'index cluster ont maintenant environ 322 500 pages de données supplémentaires à lire. Veuillez consulter la section détaillée ci-dessous pour connaître les ramifications supplémentaires de ce type de compression.
Je mettrais en garde contre toute refactorisation basée sur les performances de
SELECT TOP 1000 *
. Il est peu probable qu'il s'agisse d'une requête que l'application émettra, et ne doit pas être utilisée comme seule base pour des optimisations potentiellement inutiles.Pour des informations plus détaillées et d'autres tests à essayer, veuillez consulter la section ci-dessous.
Cette question ne peut pas recevoir de réponse définitive, mais nous pouvons au moins faire des progrès et suggérer des recherches supplémentaires pour nous aider à nous rapprocher de la détermination du problème exact (idéalement basé sur des preuves).
Ce que nous savons:
XML
colonne et plusieurs autres colonnes de types:INT
,BIGINT
,UNIQUEIDENTIFIER
, « etc »XML
la "taille" de la colonne est en moyenne d' environ 15kDBCC DROPCLEANBUFFERS
, il faut 20 à 25 secondes pour que la requête suivante se termine:SELECT TOP 1000 * FROM TABLE
Ce que nous pensons savoir:
La compression XML pourrait vous aider. Comment feriez-vous exactement la compression dans .NET? Via les classes GZipStream ou DeflateStream ? Ce n'est pas une option à coût nul. Il compressera certainement certaines des données d'un grand pourcentage, mais il faudra également plus de CPU car vous aurez besoin d'un processus supplémentaire pour compresser / décompresser les données à chaque fois. Ce plan supprimerait également complètement votre capacité à:
.nodes
,.value
,.query
et les.modify
fonctions XML.indexer les données XML.
Veuillez garder à l'esprit (puisque vous avez mentionné que XML est "hautement redondant") que le
XML
type de données est déjà optimisé en ce qu'il stocke les noms d'élément et d'attribut dans un dictionnaire, en attribuant un ID d'index entier à chaque élément, puis en utilisant cet ID entier dans tout le document (par conséquent, il ne répète pas le nom complet pour chaque utilisation, ni ne le répète à nouveau comme une balise de fermeture pour les éléments). Les données réelles ont également supprimé les espaces blancs superflus. C'est pourquoi les documents XML extraits ne conservent pas leur structure d'origine et pourquoi les éléments vides sont extraits comme<element />
s'ils étaient entrés en tant que<element></element>
. Ainsi, tout gain de compression via GZip (ou toute autre chose) ne sera trouvé qu'en compressant les valeurs des éléments et / ou des attributs, ce qui est une surface beaucoup plus petite qui pourrait être améliorée que la plupart ne le pensent et ne vaut probablement pas la perte de capacités comme indiqué ci-dessus.Veuillez également garder à l'esprit que la compression des données XML et le stockage du
VARBINARY(MAX)
résultat n'éliminera pas l'accès LOB, il le réduira simplement. Selon la taille du reste des données sur la ligne, la valeur compressée peut tenir dans la ligne, ou elle peut encore nécessiter des pages LOB.Ces informations, bien qu'utiles, ne suffisent pas. Il existe de nombreux facteurs qui influencent les performances des requêtes, nous avons donc besoin d'une image beaucoup plus détaillée de ce qui se passe.
Ce que nous ne savons pas, mais devons:
SELECT *
matière? Est-ce un modèle que vous utilisez dans le code. Si oui, pourquoi?SELECT TOP 1000 XmlColumn FROM TABLE;
:?La durée des 20 à 25 secondes nécessaires pour renvoyer ces 1 000 lignes est liée aux facteurs réseau (obtention des données via le câble) et la quantité est liée aux facteurs clients (soit environ 15 Mo plus le reste des non Données XML dans la grille dans SSMS, ou éventuellement sauvegarde sur disque)?
La prise en compte de ces deux aspects de l'opération peut parfois se faire simplement en ne renvoyant pas les données. Maintenant, on pourrait penser à sélectionner une table temporaire ou une variable de table, mais cela ne ferait qu'introduire quelques nouvelles variables (par exemple, les E / S de disque pour
tempdb
, les écritures du journal des transactions, la possible croissance automatique des données tempdb et / ou du fichier journal, le besoin dans le pool de tampons, etc.). Tous ces nouveaux facteurs peuvent en fait augmenter le temps de requête. Au lieu de cela, je stocke généralement les colonnes dans des variables (du type de données approprié; nonSQL_VARIANT
) qui sont écrasées à chaque nouvelle ligne (c'est-à-direSELECT @Column1 = tab.Column1,...
).CEPENDANT , comme l'a souligné @PaulWhite dans ce DBA.StackExchange Q & A, Logical lit différemment lors de l'accès aux mêmes données LOB , avec des recherches supplémentaires de moi-même publiées sur PasteBin ( script T-SQL pour tester divers scénarios pour les lectures LOB ) , LOB ne sont pas accessibles constamment entre
SELECT
,SELECT INTO
,SELECT @XmlVariable = XmlColumn
,SELECT @XmlVariable = XmlColumn.query(N'/')
etSELECT @NVarCharVariable = CONVERT(NVARCHAR(MAX), XmlColumn)
. Nos options sont donc un peu plus limitées ici, mais voici ce qui peut être fait:Sinon, vous pouvez exécuter la requête via Sqlcmd.exe et diriger la sortie pour aller nulle part via:
-o NUL:
.Quelle est la taille réelle des données pour les
XML
colonnes renvoyées ? La taille moyenne de cette colonne sur l'ensemble du tableau n'a pas vraiment d'importance si les lignes "TOP 1000" contiennent une partie disproportionnée du total desXML
données. Si vous voulez en savoir plus sur les 1000 premières lignes, regardez ces lignes. Veuillez exécuter ce qui suit:CREATE TABLE
, y compris tous les index.Quels sont les résultats exacts de la requête suivante:
MISE À JOUR
Il m'est venu à l'esprit que je devrais essayer de reproduire ce scénario pour voir si je rencontre un comportement similaire. J'ai donc créé un tableau avec plusieurs colonnes (similaire à la description vague de la question), puis je l'ai rempli avec 1 million de lignes, et la colonne XML a environ 15k de données par ligne (voir le code ci-dessous).
Ce que j'ai trouvé, c'est que faire un
SELECT TOP 1000 * FROM TABLE
terminé en 8 secondes la première fois, et 2 à 4 secondes à chaque fois par la suite (oui, exécuterDBCC DROPCLEANBUFFERS
avant chaque exécution de laSELECT *
requête). Et mon ordinateur portable de plusieurs années n'est pas rapide: SQL Server 2012 SP2 Developer Edition, 64 bits, 6 Go de RAM, double 2,5 Ghz Core i5 et un lecteur SATA à 5400 tr / min. J'utilise également SSMS 2014, SQL Server Express 2014, Chrome et plusieurs autres choses.Sur la base du temps de réponse de mon système, je répéterai que nous avons besoin de plus d'informations (c'est-à-dire des détails sur le tableau et les données, les résultats des tests suggérés, etc.) afin d'aider à réduire la cause du temps de réponse de 20 à 25 secondes que vous voyez.
Et, parce que nous voulons prendre en compte le temps nécessaire pour lire les pages non LOB, j'ai exécuté la requête suivante pour sélectionner tout sauf la colonne XML (l'un des tests que j'ai suggéré ci-dessus). Cela revient en 1,5 seconde assez régulièrement.
Conclusion (pour le moment)
Sur la base de ma tentative de recréer votre scénario, je ne pense pas que nous puissions indiquer le lecteur SATA ou les E / S non séquentielles comme la principale cause des 20 à 25 secondes, surtout parce que nous Je ne sais pas à quelle vitesse la requête retourne sans inclure la colonne XML. Et je n'ai pas pu reproduire le grand nombre de lectures logiques (non LOB) que vous montrez, mais j'ai le sentiment que j'ai besoin d'ajouter plus de données à chaque ligne à la lumière de cela et de la déclaration de:
Ma table comporte 1 million de lignes, chacune
sys.dm_db_index_physical_stats
contenant un peu plus de 15 000 données XML, et montre qu'il y a 2 millions de pages LOB_DATA. Les 10% restants seraient alors 222 000 pages de données IN_ROW, mais je n'en ai que 11 630. Donc, encore une fois, nous avons besoin de plus d'informations sur le schéma de table réel et les données réelles.la source
Oui, la lecture de données LOB non stockées en ligne conduit à des E / S aléatoires au lieu d'E / S séquentielles. La mesure des performances du disque à utiliser ici pour comprendre pourquoi elle est rapide ou lente est IOPS à lecture aléatoire.
Les données LOB sont stockées dans une arborescence où la page de données dans l'index clusterisé pointe vers une page de données LOB avec une structure racine LOB qui à son tour pointe vers les données LOB réelles. Lors de la traversée des nœuds racine dans l'index cluster, SQL Server ne peut obtenir les données en ligne que par des lectures séquentielles. Pour obtenir les données LOB, SQL Server doit aller ailleurs sur le disque.
Je suppose que si vous passiez à un disque SSD, vous n'en souffririez pas beaucoup, car les IOPS aléatoires pour un SSD sont bien plus élevés que pour un disque en rotation.
Oui, ça pourrait l'être. Cela dépend de ce que ce tableau fait pour vous.
Habituellement, les problèmes de performances avec XML dans SQL Server se produisent lorsque vous souhaitez utiliser T-SQL pour interroger le XML et plus encore lorsque vous souhaitez utiliser des valeurs du XML dans un prédicat dans une clause where ou une jointure. Si tel est le cas, vous pouvez consulter la promotion des propriétés ou les index XML sélectifs ou une refonte de vos structures de table déchiquetant le XML en tables à la place.
Je l'ai fait une fois dans un produit il y a un peu plus de 10 ans et je le regrette depuis. J'ai vraiment manqué de ne pas pouvoir travailler avec les données en utilisant T-SQL, donc je ne recommanderais cela à personne si cela peut être évité.
la source