J'avais l'impression que si je sommais DATALENGTH()
tous les champs pour tous les enregistrements d'une table, j'obtiendrais la taille totale de la table. Suis-je trompé?
SELECT
SUM(DATALENGTH(Field1)) +
SUM(DATALENGTH(Field2)) +
SUM(DATALENGTH(Field3)) TotalSizeInBytes
FROM SomeTable
WHERE X, Y, and Z are true
J'ai utilisé cette requête ci-dessous (que j'ai obtenue en ligne pour obtenir les tailles de table, les index cluster uniquement afin qu'elle n'inclue pas les index NC) pour obtenir la taille d'une table particulière dans ma base de données. À des fins de facturation (nous facturons nos services en fonction de la quantité d'espace qu'ils utilisent), je dois déterminer la quantité d'espace utilisée par chaque service dans ce tableau. J'ai une requête qui identifie chaque groupe dans la table. J'ai juste besoin de comprendre combien d'espace chaque groupe occupe.
L'espace par ligne peut osciller énormément en raison des VARCHAR(MAX)
champs dans la table, donc je ne peux pas simplement prendre une taille moyenne * le ratio de lignes pour un département. Lorsque j'utilise l' DATALENGTH()
approche décrite ci-dessus, je n'obtiens que 85% de l'espace total utilisé dans la requête ci-dessous. Pensées?
SELECT
s.Name AS SchemaName,
t.NAME AS TableName,
p.rows AS RowCounts,
(SUM(a.total_pages) * 8)/1024 AS TotalSpaceMB,
(SUM(a.used_pages) * 8)/1024 AS UsedSpaceMB,
((SUM(a.total_pages) - SUM(a.used_pages)) * 8)/1024 AS UnusedSpaceMB
FROM
sys.tables t with (nolock)
INNER JOIN
sys.schemas s with (nolock) ON s.schema_id = t.schema_id
INNER JOIN
sys.indexes i with (nolock) ON t.OBJECT_ID = i.object_id
INNER JOIN
sys.partitions p with (nolock) ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
INNER JOIN
sys.allocation_units a with (nolock) ON p.partition_id = a.container_id
WHERE
t.is_ms_shipped = 0
AND i.OBJECT_ID > 255
AND i.type_desc = 'Clustered'
GROUP BY
t.Name, s.Name, p.Rows
ORDER BY
TotalSpaceMB desc
Il a été suggéré de créer un index filtré pour chaque département ou de partitionner la table, afin de pouvoir interroger directement l'espace utilisé par index. Des index filtrés pourraient être créés par programme (et supprimés à nouveau pendant une fenêtre de maintenance ou lorsque j'ai besoin d'effectuer la facturation périodique), au lieu d'utiliser l'espace tout le temps (les partitions seraient meilleures à cet égard).
J'aime cette suggestion et je le fais généralement. Mais pour être honnête, j'utilise "chaque département" comme exemple pour expliquer pourquoi j'ai besoin de cela, mais pour être honnête, ce n'est pas vraiment pourquoi. Pour des raisons de confidentialité, je ne peux pas expliquer la raison exacte pour laquelle j'ai besoin de ces données, mais c'est analogue à différents départements.
Concernant les index non cluster sur cette table: Si je peux obtenir les tailles des index NC, ce serait génial. Cependant, les index NC représentent <1% de la taille de l'index clusterisé, nous sommes donc d'accord de ne pas les inclure. Cependant, comment pourrions-nous inclure les index NC de toute façon? Je ne peux même pas obtenir une taille précise pour l'index clusterisé :)
Réponses:
Please note that the following info is not intended to be a comprehensive
description of how data pages are laid out, such that one can calculate
the number of bytes used per any set of rows, as that is very complicated.
Les données ne sont pas la seule chose qui prend de la place sur une page de données 8k:
Il y a un espace réservé. Vous n'êtes autorisé à utiliser que 8060 des 8192 octets (c'est 132 octets qui ne vous ont jamais appartenu en premier lieu):
DBCC PAGE
, c'est pourquoi elle est conservée séparément ici au lieu d'être incluse dans les informations par ligne ci-dessous.NULL
. 1 octet pour chaque ensemble de 8 colonnes. Et pour toutes les colonnes, mêmeNOT NULL
celles. Par conséquent, au moins 1 octet.ALLOW_SNAPSHOT_ISOLATION ON
ouREAD_COMMITTED_SNAPSHOT ON
).Pointeurs LOB pour les données qui ne sont pas stockées en ligne. Cela représenterait donc
DATALENGTH
+ pointer_size. Mais ceux-ci ne sont pas de taille standard. Veuillez consulter le billet de blog suivant pour plus de détails sur ce sujet complexe: Quelle est la taille du pointeur LOB pour les types (MAX) comme Varchar, Varbinary, Etc? . Entre ce message lié et certains tests supplémentaires que j'ai effectués , les règles (par défaut) devraient être les suivantes:TEXT
,NTEXT
etIMAGE
):text in row
option, alors:VARCHAR(MAX)
,NVARCHAR(MAX)
etVARBINARY(MAX)
):large value types out of row
option, utilisez toujours un pointeur de 16 octets vers le stockage LOB.Pages de débordement LOB: si une valeur est de 10 ko, cela nécessitera 1 page complète de 8 ko de débordement, puis une partie d'une 2e page. Si aucune autre donnée ne peut occuper l'espace restant (ou y est même autorisée, je ne suis pas sûr de cette règle), alors vous avez environ 6 Ko d'espace "gaspillé" sur cette deuxième page de débordement de LOB.
Espace inutilisé: Une page de données de 8k est juste cela: 8192 octets. Il ne varie pas en taille. Les données et métadonnées qui y sont placées, cependant, ne s'intègrent pas toujours bien dans les 8192 octets. Et les lignes ne peuvent pas être divisées sur plusieurs pages de données. Donc, s'il vous reste 100 octets mais qu'aucune ligne (ou aucune ligne qui pourrait tenir à cet emplacement, en fonction de plusieurs facteurs) ne peut y tenir, la page de données occupe toujours 8192 octets, et votre deuxième requête ne compte que le nombre de pages de données. Vous pouvez trouver cette valeur à deux endroits (gardez juste à l'esprit qu'une partie de cette valeur est une certaine quantité de cet espace réservé):
DBCC PAGE( db_name, file_id, page_id ) WITH TABLERESULTS;
Recherchez = "EN-TÊTE DEParentObject
PAGE:" etField
= "m_freeCnt". LeValue
champ est le nombre d'octets inutilisés.SELECT buff.free_space_in_bytes FROM sys.dm_os_buffer_descriptors buff WHERE buff.[database_id] = DB_ID(N'db_name') AND buff.[page_id] = page_id;
Il s'agit de la même valeur que celle signalée par "m_freeCnt". C'est plus facile que DBCC car il peut obtenir de nombreuses pages, mais nécessite également que les pages aient été lues dans le pool de tampons en premier lieu.Espace réservé par
FILLFACTOR
<100. Les pages nouvellement créées ne respectent pas leFILLFACTOR
paramètre, mais effectuer une RECONSTRUCTION réservera cet espace sur chaque page de données. L'idée derrière l'espace réservé est qu'il sera utilisé par des insertions et / ou des mises à jour non séquentielles qui augmentent déjà la taille des lignes sur la page, en raison de la mise à jour des colonnes de longueur variable avec un peu plus de données (mais pas assez pour provoquer un page-split). Mais vous pouvez facilement réserver de l'espace sur des pages de données qui n'obtiendraient naturellement jamais de nouvelles lignes et n'auraient jamais les lignes existantes mises à jour, ou du moins pas mises à jour d'une manière qui augmenterait la taille de la ligne.Page-Splits (fragmentation): La nécessité d'ajouter une ligne à un emplacement qui n'a pas de place pour la ligne entraînera une division de la page. Dans ce cas, environ 50% des données existantes sont déplacées vers une nouvelle page et la nouvelle ligne est ajoutée à l'une des 2 pages. Mais vous avez maintenant un peu plus d'espace libre qui n'est pas pris en compte par les
DATALENGTH
calculs.Lignes marquées pour suppression. Lorsque vous supprimez des lignes, elles ne sont pas toujours immédiatement supprimées de la page de données. S'ils ne peuvent pas être supprimés immédiatement, ils sont "marqués à mort" (référence Steven Segal) et seront physiquement supprimés plus tard par le processus de nettoyage des fantômes (je crois que c'est le nom). Cependant, ceux-ci pourraient ne pas être pertinents pour cette Question particulière.
Pages fantômes? Je ne sais pas si c'est le terme approprié, mais parfois les pages de données ne sont pas supprimées tant qu'une RECONSTRUCTION de l'index clusterisé n'est pas terminée. Cela représenterait également plus de pages qu'il n'en
DATALENGTH
faudrait. Cela ne devrait généralement pas se produire, mais je l'ai rencontré une fois, il y a plusieurs années.Colonnes SPARSE: les colonnes éparses économisent de l'espace (principalement pour les types de données de longueur fixe) dans les tables où un grand% des lignes sont
NULL
pour une ou plusieurs colonnes. L'SPARSE
option faitNULL
monter le type de valeur de 0 octet (au lieu de la quantité de longueur fixe normale, comme 4 octets pour unINT
), mais les valeurs non NULL occupent chacune 4 octets supplémentaires pour les types de longueur fixe et une quantité variable pour types de longueur variable. Le problème ici est qu'ilDATALENGTH
n'inclut pas les 4 octets supplémentaires pour les valeurs non NULL dans une colonne SPARSE, donc ces 4 octets doivent être ajoutés à nouveau. Vous pouvez vérifier s'il y a desSPARSE
colonnes via:Et puis pour chaque
SPARSE
colonne, mettez à jour la requête d'origine pour utiliser:Veuillez noter que le calcul ci-dessus pour ajouter 4 octets standard est un peu simpliste car il ne fonctionne que pour les types de longueur fixe. ET, il y a des métadonnées supplémentaires par ligne (d'après ce que je peux dire jusqu'à présent) qui réduisent l'espace disponible pour les données, simplement en ayant au moins une colonne SPARSE. Pour plus de détails, veuillez consulter la page MSDN pour Utiliser des colonnes éparses .
Index et autres pages (par exemple IAM, PFS, GAM, SGAM, etc.): ce ne sont pas des pages "données" en termes de données utilisateur. Ceux-ci gonfleront la taille totale de la table. Si vous utilisez SQL Server 2012 ou une version plus récente, vous pouvez utiliser la
sys.dm_db_database_page_allocations
fonction de gestion dynamique (DMF) pour voir les types de page (les versions antérieures de SQL Server peuvent utiliserDBCC IND(0, N'dbo.table_name', 0);
):Ni le
DBCC IND
nisys.dm_db_database_page_allocations
(avec cette clause WHERE) ne signalera aucune page d'index, et seul leDBCC IND
rapportera au moins une page IAM.DATA_COMPRESSION: Si vous avez activé
ROW
ouPAGE
Compression activé sur l'index cluster ou le tas, vous pouvez oublier la plupart de ce qui a été mentionné jusqu'à présent. L'en-tête de page de 96 octets, le tableau des emplacements de 2 octets par ligne et les informations de version de 14 octets par ligne sont toujours là, mais la représentation physique des données devient très complexe (beaucoup plus que ce qui a déjà été mentionné lors de la compression). n'est pas utilisé). Par exemple, avec la compression de lignes, SQL Server tente d'utiliser le plus petit conteneur possible pour s'adapter à chaque colonne, pour chaque ligne. Donc, si vous avez uneBIGINT
colonne qui, autrement (en supposant qu'elleSPARSE
n'est pas également activée), prendrait toujours 8 octets, si la valeur est comprise entre -128 et 127 (c'est-à-dire un entier signé de 8 bits), elle n'utilisera qu'un seul octet, et si le la valeur pourrait tenir dans unSMALLINT
, il ne prendra que 2 octets. Les types entiers qui sontNULL
ou ne0
prennent pas d'espace et sont simplement indiqués comme étantNULL
ou "vides" (c'est-à-dire0
) dans un tableau mappant les colonnes. Et il y a beaucoup, beaucoup d'autres règles. Contiennent des données Unicode (NCHAR
,NVARCHAR(1 - 4000)
mais pasNVARCHAR(MAX)
, même si elles sont stockées en ligne)? La compression Unicode a été ajoutée dans SQL Server 2008 R2, mais il n'y a aucun moyen de prédire le résultat de la valeur "compressée" dans toutes les situations sans effectuer la compression réelle étant donné la complexité des règles .Donc, en réalité, votre deuxième requête, bien que plus précise en termes d'espace physique total occupé sur le disque, n'est vraiment précise qu'en effectuant un
REBUILD
de l'index clusterisé. Et après cela, vous devez toujours tenir compte de toutFILLFACTOR
paramètre inférieur à 100. Et même dans ce cas, il y a toujours des en-têtes de page, et souvent suffisamment d'espace "gaspillé" qui n'est tout simplement pas remplissable car trop petit pour tenir dans une ligne de ce table, ou au moins la ligne qui devrait logiquement aller dans cet emplacement.En ce qui concerne la précision de la 2e requête dans la détermination de l '"utilisation des données", il semble plus juste de sauvegarder les octets d'en-tête de page car ils ne sont pas des données: ils représentent des frais généraux liés au coût de l'entreprise. S'il y a 1 ligne sur une page de données et que cette ligne n'est qu'un
TINYINT
, alors cet octet exigeait toujours que la page de données existe et donc les 96 octets de l'en-tête. Ce service devrait-il être facturé pour toute la page de données? Si cette page de données est ensuite remplie par le ministère # 2, répartiraient-ils également ces frais généraux ou paieraient-ils proportionnellement? Il semble plus facile de le retirer. Dans ce cas, l'utilisation d'une valeur de8
pour se multipliernumber of pages
est trop élevée. Que diriez-vous:Par conséquent, utilisez quelque chose comme:
pour tous les calculs par rapport aux colonnes "number_of_pages".
ET , considérant que l'utilisation
DATALENGTH
par chaque champ ne peut pas renvoyer les métadonnées par ligne, qui doivent être ajoutées à votre requête par table où vous obtenez leDATALENGTH
par chaque champ, en filtrant sur chaque "département":ALLOW_SNAPSHOT_ISOLATION
ou estREAD_COMMITTED_SNAPSHOT
définie surON
)NULL
, et si la valeur tient sur la ligne, elle peut être beaucoup plus petite ou beaucoup plus grande que le pointeur, et si la valeur est stockée hors- ligne, la taille du pointeur peut dépendre de la quantité de données. Cependant, puisque nous voulons juste une estimation (c'est-à-dire "swag"), il semble que 24 octets soit une bonne valeur à utiliser (enfin, aussi bonne que toute autre ;-). C'est pour chaqueMAX
champ.Par conséquent, utilisez quelque chose comme:
En général (en-tête de ligne + nombre de colonnes + tableau d'emplacements + bitmap NULL):
En général (détection automatique si des "informations de version" sont présentes):
S'il existe des colonnes de longueur variable, ajoutez:
S'il y a des
MAX
colonnes / LOB, ajoutez:En général:
Ce n'est pas exact, et encore une fois ne fonctionnera pas si vous avez activé la compression de ligne ou de page sur le tas ou l'index clusterisé, mais devrait certainement vous rapprocher.
MISE À JOUR concernant le mystère de la différence de 15%
Nous (moi-même inclus) étions tellement concentrés sur la façon dont les pages de données sont disposées et sur la façon dont
DATALENGTH
pourraient expliquer les choses que nous n'avons pas passé beaucoup de temps à examiner la deuxième requête. J'ai exécuté cette requête sur une seule table, puis j'ai comparé ces valeurs à ce qui était signalé parsys.dm_db_database_page_allocations
et ce n'étaient pas les mêmes valeurs pour le nombre de pages. Sur une intuition, j'ai supprimé les fonctions d'agrégation etGROUP BY
, et remplacé laSELECT
liste para.*, '---' AS [---], p.*
. Et puis, il est devenu clair: les gens doivent faire attention d'où sur ces interwebs troubles ils obtiennent leurs informations et leurs scripts ;-). La deuxième requête publiée dans la question n'est pas exactement correcte, en particulier pour cette question particulière.Problème mineur: en dehors de cela n'a pas beaucoup de sens pour
GROUP BY rows
(et ne pas avoir cette colonne dans une fonction d'agrégation), le JOIN entresys.allocation_units
etsys.partitions
n'est pas techniquement correct. Il existe 3 types d'unités d'allocation, et l'un d'entre eux doit se joindre à un champ différent. Assez souventpartition_id
ethobt_id
sont les mêmes, donc il pourrait ne jamais y avoir de problème, mais parfois ces deux champs ont des valeurs différentes.Problème majeur: la requête utilise le
used_pages
champ. Ce champ couvre tous les types de pages: données, index, IAM, etc., tc. Il y a un autre, champ plus approprié à utiliser lorsque concerné que les données réelles:data_pages
.J'ai adapté la 2e requête de la question en gardant à l'esprit les éléments ci-dessus et en utilisant la taille de la page de données qui sauvegarde l'en-tête de la page. J'ai aussi enlevé deux JOIN qui étaient inutiles:
sys.schemas
(remplacé par appelSCHEMA_NAME()
), etsys.indexes
(l'index cluster est toujoursindex_id = 1
et nous avonsindex_id
ensys.partitions
).la source
C'est peut-être une réponse grunge, mais c'est ce que je ferais.
La DATALENGTH ne représente donc que 86% du total. C'est encore une division très représentative. Les frais généraux dans l'excellente réponse de srutzky devraient avoir une répartition assez uniforme.
J'utiliserais votre deuxième requête (pages) pour le total. Et utilisez la première (longueur de données) pour allouer le partage. De nombreux coûts sont répartis en utilisant une normalisation.
Et vous devez considérer qu'une réponse plus proche va augmenter les coûts, donc même le département qui a perdu sur une scission peut toujours payer plus.
la source