512 byte non vengono utilizzati dalla pagina di dati 8 KByte di SQL Server


13

Ho creato la seguente tabella:

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

e quindi creato un indice cluster:

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

Successivamente l'ho popolato con 30 righe ogni dimensione è 256 byte (in base alla dichiarazione della tabella):

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;

Ora basato sulle informazioni che ho letto nel "Kit di formazione (Esame 70-461): Interrogazione del libro Microsoft SQL Server 2012 (Itzik Ben-Gan)":

SQL Server organizza internamente i dati in un file di dati in pagine. Una pagina è un'unità da 8 KB e appartiene a un singolo oggetto; ad esempio, a una tabella o un indice. Una pagina è la più piccola unità di lettura e scrittura. Le pagine sono ulteriormente organizzate in estensione. Un'estensione è composta da otto pagine consecutive. Le pagine di un'estensione possono appartenere a un singolo oggetto o a più oggetti. Se le pagine appartengono a più oggetti, l'estensione viene chiamata estensione mista; se le pagine appartengono a un singolo oggetto, l'estensione viene definita estensione uniforme. SQL Server archivia le prime otto pagine di un oggetto in estensioni miste. Quando un oggetto supera le otto pagine, SQL Server alloca ulteriori estensioni uniformi per questo oggetto. Con questa organizzazione, i piccoli oggetti sprecano meno spazio e gli oggetti più grandi sono meno frammentati.

Quindi qui ho la prima pagina di 8 KB di dimensioni miste, popolata con 7680 byte (ho inserito 30 volte 256 byte di dimensione riga, quindi 30 * 256 = 7680), per verificare la dimensione che ho eseguito proc verifica dimensioni - restituisce il seguente risultato

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

Quindi 16 KB sono riservati per la tabella, la prima pagina da 8 KB è per la pagina Root IAM, la seconda è per la pagina di archiviazione dei dati foglia che è 8KB con un'occupazione di ~ 7.5 KB, ora quando inserisco una nuova riga con 256 byte:

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

non è memorizzato nella stessa pagina sebbene abbia uno spazio di 256 byte (7680 b + 256 = 7936 che è ancora più piccolo di 8 KB), viene creata una nuova pagina di dati, ma quella nuova riga potrebbe adattarsi alla stessa vecchia pagina , perché SQL Server crea una nuova pagina quando è possibile risparmiare spazio e tempo di ricerca acquistandolo inserendolo nella pagina esistente?

Nota: sta succedendo la stessa cosa nell'indice heap.

Risposte:


9

Le righe di dati non sono 256 byte. Ognuno è più simile a 263 byte. Una riga di dati di tipi di dati a lunghezza puramente fissa ha un sovraccarico aggiuntivo a causa della struttura di una riga di dati in SQL Server. Dai un'occhiata a questo sito e leggi come è composta una riga di dati. http://aboutsqlserver.com/2013/10/15/sql-server-storage-engine-data-pages-and-data-rows/

Quindi, nel tuo esempio, hai una riga di dati che ha 256 byte, aggiungi 2 byte per i bit di stato, 2 byte per il numero di colonne, 2 byte per la lunghezza dei dati e un altro 1 circa per bitmap null. Cioè 263 * 30 = 7.890 byte. Aggiungi un altro 263 e hai superato il limite di 8kb della pagina che forzerebbe la creazione di un'altra pagina.


Il link che mi hai fornito mi ha aiutato a visualizzare meglio la struttura della pagina, stavo cercando qualcosa di simile ma non riuscivo a trovarlo, Thax
Alphas Supremum

11

Mentre è vero che SQL Server utilizza pagine di dati 8k (8192 byte) per memorizzare 1 o più righe, ogni pagina di dati ha un sovraccarico (96 byte) e ogni riga ha un sovraccarico (almeno 9 byte). Gli 8192 byte non sono puramente dati.

Per un esame più dettagliato di come funziona, consulta la mia risposta alla seguente domanda DBA.SE:

SOMMA di DATALENGTH che non corrispondono alle dimensioni della tabella da sys.allocation_units

Usando le informazioni in quella risposta collegata, possiamo ottenere un'immagine più chiara della dimensione effettiva della riga:

  1. Intestazione riga = 4 byte
  2. Numero di colonne = 2 byte
  3. NULL Bitmap = 1 byte
  4. Informazioni sulla versione ** = 14 byte (opzionale, vedi nota a piè di pagina)
  5. Overhead totale per riga (escluso array di slot) = minimo 7 byte o 21 byte se sono presenti informazioni sulla versione
  6. Dimensione totale effettiva riga = minimo 263 (256 dati + 7 sovraccarico) o 277 byte (256 dati + 21 sovraccarico) se sono presenti informazioni sulla versione
  7. Aggiungendo nell'array di slot, lo spazio totale occupato per riga è in realtà di 265 byte (senza informazioni sulla versione) o 279 byte (con informazioni sulla versione).

L'utilizzo DBCC PAGEconferma il mio calcolo mostrando: Record Size 263(per tempdb) e Record Size 277(per un database impostato suALLOW_SNAPSHOT_ISOLATION ON ).

Ora, con 30 righe, ovvero:

  • SENZA informazioni sulla versione

    30 * 263 ci darebbero 7890 byte. Quindi aggiungere i 96 byte dell'intestazione della pagina per 7986 byte utilizzati. Infine, aggiungi i 60 byte (2 per riga) dell'array di slot per un totale di 8046 byte utilizzati nella pagina e 146 rimanenti. L'utilizzo DBCC PAGEconferma il mio calcolo mostrando:

    • m_slotCnt 30 (es. numero di file)
    • m_freeCnt 146 (ovvero il numero di byte rimasti nella pagina)
    • m_freeData 7986 (ad es. dati + intestazione di pagina - 7890 + 96 - l'array di slot non viene preso in considerazione nel calcolo dei byte "usati")
  • CON informazioni sulla versione

    30 * 277 byte per un totale di 8310 byte. Ma 8310 è oltre 8192 e ciò non ha nemmeno tenuto conto dell'intestazione della pagina a 96 byte né dell'array di slot a 2 byte per riga (30 * 2 = 60 byte) che dovrebbe fornire solo 8036 byte utilizzabili per le righe.

    MA, per quanto riguarda 29 file? Ciò ci darebbe 8033 byte di dati (29 * 277) + 96 byte per l'intestazione della pagina + 58 byte per array di slot (29 * 2) pari a 8187 byte. E questo lascerebbe la pagina con 5 byte rimanenti (8192-8187; inutilizzabile, ovviamente). L'utilizzo DBCC PAGEconferma il mio calcolo mostrando:

    • m_slotCnt 29 (es. numero di file)
    • m_freeCnt 5 (ovvero il numero di byte rimasti nella pagina)
    • m_freeData 8129 (ovvero dati + intestazione di pagina - 8033 + 96 - l'array di slot non viene preso in considerazione nel calcolo dei byte "usati")

Per quanto riguarda i cumuli

Gli heap riempiono le pagine di dati in modo leggermente diverso. Mantengono una stima molto approssimativa della quantità di spazio lasciato sulla pagina. Se si guarda alla output DBCC, guardare la riga per: PAGE HEADER: Allocation Status PFS (1:1). Vedrai VALUEmostrare qualcosa sulla falsariga di 0x60 MIXED_EXT ALLOCATED 0_PCT_FULL(quando ho guardato la tabella in cluster) o0x64 MIXED_EXT ALLOCATED 100_PCT_FULL quando si guarda la tabella di Heap. Questo viene valutato per Transazione, quindi eseguire inserimenti individuali come il test eseguito qui potrebbe mostrare risultati diversi tra le tabelle Cluster e Heap. Fare una singola operazione DML per tutte e 30 le righe, tuttavia, riempirà l'heap come previsto.

Tuttavia, nessuno di questi dettagli riguardanti Heaps influenza direttamente questo particolare test poiché entrambe le versioni della tabella si adattano a 30 righe con solo 146 byte rimanenti. Questo non è abbastanza spazio per un'altra riga, indipendentemente da Cluster o Heap.

Tieni presente che questo test è piuttosto semplice. Il calcolo della dimensione effettiva di una riga può diventare molto complicato a seconda di vari fattori, come SPARSE:, Compressione dei dati, dati LOB, ecc.


Per visualizzare i dettagli della pagina dei dati, utilizzare la seguente query:

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;

** Il valore "info versione" a 14 byte sarà presente se il database è impostato su ALLOW_SNAPSHOT_ISOLATION ONo READ_COMMITTED_SNAPSHOT ON.


Per la precisione sono disponibili 8060 byte per pagina per i dati dell'utente. I dati del PO sono ancora inferiori.
Roger Wolf,

Le informazioni sulla versione non sono presenti, altrimenti 30 righe richiederebbero 8310 byte. Il resto sembra corretto.
Roger Wolf,

@RogerWolf Sì, "Informazioni sulla versione" è presente. E quindi sì, 30 righe richiedono 8310 byte. Questo è il motivo per cui quelle 30 righe non sono state tutte inserite in 1 pagina poiché l'OP viene portato a credere da qualsiasi processo di prova utilizzato dall'OP. Ma quel proc di prova è sbagliato. Solo 29 righe si adattano alla pagina. L'ho confermato (anche usando SQL Server 2012).
Solomon Rutzky,

hai provato a eseguire il test su un database che non è abilitato per RCSI / tempdb? Sono stato in grado di riprodurre i numeri esatti forniti da OP.
Roger Wolf,

@RogerWolf Il DB che sto usando non è abilitato per RCSI, ma è impostato su ALLOW_SNAPSHOT_ISOLATION. Ho anche provato tempdbe ho visto che le "informazioni sulla versione" non sono lì, quindi 30 righe si adattano. Aggiornerò per aggiungere le nuove informazioni.
Solomon Rutzky,

3

L'attuale struttura della pagina dei dati è piuttosto complessa. Mentre si afferma generalmente che per i dati utente sono disponibili 8060 byte per pagina, vi è un sovraccarico aggiuntivo non conteggiato da nessuna parte che provoca questo comportamento.

Tuttavia, potresti aver notato che SQL Server in realtà ti dà un suggerimento che la 31a riga non si adatta alla pagina. Affinché la riga successiva si adatti alla stessa pagina, il avg_page_space_used_in_percentvalore dovrebbe essere inferiore al 100% - (100/31) = 96.774194, e nel tuo caso è molto superiore.

PS Credo di aver visto una spiegazione dettagliata, fino al byte, della struttura della pagina di dati in uno dei libri "SQL Server Internals" di Kalen Delaney, ma è stato quasi 10 anni fa, quindi mi scusi per non ricordare altri dettagli. Inoltre, la struttura della pagina tende a cambiare da una versione all'altra.


1
No. L'unicificatore viene aggiunto solo a righe chiave duplicate. La prima riga di ciascun valore chiave univoco non include l'unicificatore univoco a 4 byte.
Solomon Rutzky,

@srutzky, a quanto pare hai ragione. Non avrei mai pensato che SQL Server avrebbe consentito chiavi a larghezza variabile. Questo è brutto. Efficiente, sì, ma brutto.
Roger Wolf,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.