web-dev-qa-db-fra.com

512 octets ne sont pas utilisés à partir de la page de données 8 kbyte de SQL Server

J'ai créé le tableau suivant:

CREATE TABLE dbo.TestStructure
(
    id INT NOT NULL,
    filler1 CHAR(36) NOT NULL,
    filler2 CHAR(216) NOT NULL
);

puis créé un indice en cluster:

CREATE CLUSTERED INDEX idx_cl_id 
ON dbo.TestStructure(id);

Ensuite, je l'ai peuplé avec 30 rangées, chaque taille est de 256 octets (basé sur la déclaration de table):

DECLARE @i AS int = 0;

WHILE @i < 30
BEGIN
    SET @i = @i + 1;

    INSERT INTO dbo.TestStructure (id, filler1, filler2)
    VALUES (@i, 'a', 'b');
END;

Maintenant basé sur les informations que j'ai lues dans "Kit d'entraînement (Examen 70-461): Interrogatoire Microsoft SQL Server 2012 (Itzik Ben Gan)" Book:

SQL Server organise en interne des données dans un fichier de données dans les pages. Une page est une unité de 8 kb et appartient à un seul objet; par exemple, à une table ou à un index. Une page est la plus petite unité de lecture et d'écriture. Les pages sont davantage organisées en extensions. Une mesure consiste en huit pages consécutives. Les pages d'une mesure peuvent appartenir à un seul objet ou à plusieurs objets. Si les pages appartiennent à plusieurs objets, la mesure est appelée une étendue mélangée; Si les pages appartiennent à un seul objet, la mesure est appelée une étendue uniforme. SQL Server stocke les huit premières pages d'un objet dans des extensions mixtes. Lorsqu'un objet dépasse huit pages, SQL Server attribue des étendues uniformes supplémentaires pour cet objet. Avec cette organisation, de petits objets gaspillent moins d'espace et de gros objets sont moins fragmentés.

Alors, ici, j'ai la première page mixte de 8kb, peuplée avec 7680 octets (j'ai inséré 30 fois 256 rangées de taille d'octets, donc 30 * 256 = 7680), pour vérifier la taille de la taille de la taille de la taille d'exécution. Il renvoie le résultat suivant

index_type_desc: CLUSTERED INDEX
index_depth: 1
index_level: 0 
page_count: 1 
record_count: 30 
avg_page_space_used_in_percent: 98.1961947121324
name : TestStructure        
rows : 30   
reserved :  16 KB
data : 8 KB 
index_size : 8 KB       
unused :    0 KB

Donc, 16 Ko sont réservés à la table, la première page de 8 Ko est destinée à la page Root IAM, la seconde est pour la page de stockage de données de la feuille de 8kb avec une occupation d'environ 7,5 kb, maintenant lorsque j'insère une nouvelle ligne avec 256 octets:

INSERT INTO dbo.TestStructure (id, filler1, filler2)
VALUES (1, 'a', 'b');

il n'est pas stocké dans la même page, bien qu'il ait un espace de 256 octets (7680 B + 256 = 7936 qui est toujours inférieur à 8 Ko), une nouvelle page de données est créée, mais cette nouvelle ligne pourrait être adaptée sur la même ancienne page. Pourquoi SQL Server crée-t-il une nouvelle page lorsqu'il pourrait économiser de l'espace et la recherche d'un temps d'achat d'insérez-le dans la page existante?

Remarque: la même chose se produit dans l'indice de tas.

13
Alphas Supremum

Vos lignes de données ne sont pas 256 octets. Chacun ressemble plus à 263 octets. Une ligne de données de types de données de longueur purement fixe a une surcharge supplémentaire en raison de la structure d'une ligne de données dans SQL Server. Jetez un coup d'œil à ce site et lisez sur la manière dont une ligne de données est composée. http://aboutsqlserver.com/2013/10/15/sql-server-storage-Engine-Data-Pages-and-Data-rows/

Donc, dans votre exemple, vous avez une ligne de données contenant 256Bytes, ajoutez 2 octets pour les bits d'état, 2 octets pour le nombre de colonnes, 2 octets pour la longueur de données et une autre 1 environ pour Null Bitmap. C'est 263 * 30 = 7,890Bytes. Ajoutez encore 263 et vous êtes sur la limite de la page de 8kb qui obligerait une autre page à créer.

9
dfundako

Bien qu'il soit vrai que SQL Server utilise des pages de données 8K (8192 octets) pour stocker 1 lignes ou plus, chaque page de données comporte des frais généraux (96 octets), et chaque ligne a une surcharge générale (au moins 9 octets). Les 8192 octets ne sont pas purement des données.

Pour un examen plus détaillé de la façon dont cela fonctionne, veuillez consulter ma réponse à la question DBA.SE suivante:

somme des longueurs de données ne correspondant pas la taille de la table de SYS.ALLOCATION_UNITS

En utilisant les informations dans cette réponse liée, nous pouvons obtenir une image plus claire de la taille de la ligne réelle:

  1. Row en-tête = 4 octets
  2. Nombre de colonnes = 2 octets
  3. Null bitmap = 1 octet
  4. Version infoAmateur = 14 octets (facultatif, voir note de bas de page)
  5. Total par rangée de rangée (hors réseau à sous) = 7 octets minimum, ou 21 octets si les informations de version sont présentes
  6. Taille de la rangée réelle totale = 263 minimum (256 données + 7 aérienilles) ou 277 octets (256 données + 21 horizonnées) Si la version est présente
  7. Ajout dans la matrice à sous, l'espace total pris par ligne est en fait soit 265 octets (sans informations de version), soit 279 octets (avec des informations de version).

Utiliser DBCC PAGE confirme mon calcul en montrant: Record Size 263 (pour tempdb) et Record Size 277 (pour une base de données définie sur ALLOW_SNAPSHOT_ISOLATION ON).

Maintenant, avec 30 rangées, c'est-à-dire:

  • sans informations de version

    30 * 263 nous donnerait 7890 octets. Ajoutez ensuite dans les 96 octets de la page d'en-tête de la page pour 7986 octets utilisés. Enfin, ajoutez dans les 60 octets (2 par rangée) de la matrice à sous pour un total de 8046 octets utilisés sur la page, et 146 restants. Utiliser DBCC PAGE confirme mon calcul en montrant:

    • m_slotCnt 30 (c'est-à-dire nombre de lignes)
    • m_freeCnt 146 (I.e. Nombre d'octets laissés sur la page)
    • m_freeData 7986 (i.e. Data + page Header - 7890 + 96 - La matrice à sous n'est pas prise en compte dans le calcul des octets "d'occasion")
  • avec des informations de version

    30 * 277 octets pour un total de 8310 octets. Mais 8310 est supérieur à 8192, et cela n'a même pas tenu compte de l'en-tête de la page 96 octets ni de la matrice à sous 2 octets par rangée (30 * 2 = 60 octets) qui ne devraient nous donner que 8036 octets utilisables pour les rangées.

    Mais, qu'en est-il de 29 lignes? Cela nous donnerait 8033 octets de données (29 * 277) + 96 octets pour l'en-tête de la page + 58 octets pour la matrice à sous (29 * 2) égal à 8187 octets. Et cela laisserait la page avec 5 octets restants (8192 - 8187; inutilisable, bien sûr). Utiliser DBCC PAGE confirme mon calcul en montrant:

    • m_slotCnt 29 (c'est-à-dire nombre de lignes)
    • m_freeCnt 5 (I.e. Nombre d'octets laissés sur la page)
    • m_freeData 8129 (i.e. Data + Page Header - 8033 + 96 - La matrice à sous n'est pas prise en compte dans le calcul de l'octet "utilisé")

concernant les tas

Les tas remplissent des pages de données légèrement différemment. Ils maintiennent une estimation très approximative de la quantité d'espace laissée sur la page. Lorsque vous regardez la sortie DBCC, regardez la ligne de: PAGE HEADER: Allocation Status PFS (1:1). Vous verrez le VALUE montrant quelque chose dans le sens de 0x60 MIXED_EXT ALLOCATED 0_PCT_FULL (quand j'ai regardé la table en cluster) ou 0x64 MIXED_EXT ALLOCATED 100_PCT_FULL lorsque vous regardez la table de tas. Ceci est évalué par transaction, ce faisant ainsi des inserts individuels tels que le test effectué ici pourraient montrer différents résultats entre des tables en plastique et des tables de démarrage. Faire une seule opération DML pour les 30 rangées, cependant, remplira le tas comme prévu.

Cependant, aucun de ces détails concernant les tas d'objets n'affecte directement cet essai particulier puisque les deux versions de la table FIT 30 rangées avec seulement 146 octets restant. Ce n'est pas assez d'espace pour une autre rangée, quel que soit le regroupement ou le tas.

Veuillez garder à l'esprit que ce test est plutôt simple. Calcul de la taille réelle d'une ligne peut être très compliqué en fonction de divers facteurs, tels que: SPARSE, compression de données, données LOB, etc.


Pour voir les détails de la page de données, utilisez la requête suivante:

DECLARE @PageID INT,
        @FileID INT,
        @DatabaseID SMALLINT = DB_ID();

SELECT  @FileID = alloc.[allocated_page_file_id],
        @PageID = alloc.[allocated_page_page_id]
FROM    sys.dm_db_database_page_allocations(@DatabaseID,
                            OBJECT_ID(N'dbo.TestStructure'), 1, NULL, 'DETAILED') alloc
WHERE   alloc.[previous_page_page_id] IS NULL -- first data page
AND     alloc.[page_type] = 1; -- DATA_PAGE

DBCC PAGE(@DatabaseID, @FileID, @PageID, 3) WITH TABLERESULTS;

Amateur La valeur "Version Info" de 14 octets sera présente si votre base de données est définie sur ALLOW_SNAPSHOT_ISOLATION ON ou READ_COMMITTED_SNAPSHOT ON.

11
Solomon Rutzky

La structure réelle de la page de données est assez complexe. Bien qu'il soit généralement indiqué que 8060 octets par page sont disponibles pour les données utilisateur, il existe des frais généraux supplémentaires non comptés n'importe où dans ce comportement.

Cependant, vous avez peut-être remarqué que SQL Server vous donne réellement un indice que la 31e rangée ne rentrera pas dans la page. Pour la ligne suivante adaptée à la même page, le avg_page_space_used_in_percent La valeur doit être inférieure à 100% - (100/11) = 96.774194, et dans votre cas, c'est bien au-dessus de cela.

P.s. Je crois que j'ai vu une explication détaillée de la page de la page de données dans l'un des ouvrages "SQL Server Internals" de Kalen Delaney, mais il y a presque 10 ans, veuillez m'excuser de ne pas vous souvenir de détails. En outre, la structure de la page a tendance à changer de version à la version.

3
Roger Wolf