512 Bytes tidak digunakan dari halaman data 8 KByte SQL Server

13

Saya telah membuat tabel berikut:

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

dan kemudian membuat indeks berkerumun:

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

Berikutnya saya mengisinya dengan 30 baris setiap ukuran adalah 256 byte (berdasarkan tabel deklarasi):

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;

Sekarang berdasarkan informasi yang saya baca di "Pelatihan Kit (Ujian 70-461): Meminta Microsoft SQL Server 2012 (Itzik Ben-Gan)" buku:

SQL Server secara internal mengatur data dalam file data di halaman. Halaman adalah unit 8 KB dan milik satu objek; misalnya, ke tabel atau indeks. Halaman adalah unit terkecil dari membaca dan menulis. Halaman disusun lebih lanjut menjadi luasan. Luasnya terdiri dari delapan halaman berturut-turut. Halaman dari batas tertentu dapat dimiliki oleh satu objek atau beberapa objek. Jika halaman milik beberapa objek, maka luasnya disebut tingkat campuran; jika halaman milik satu objek, maka luasnya disebut tingkat seragam. SQL Server menyimpan delapan halaman pertama objek dalam luasan campuran. Ketika suatu objek melebihi delapan halaman, SQL Server mengalokasikan luasan seragam yang seragam untuk objek ini. Dengan organisasi ini, benda-benda kecil menghabiskan lebih sedikit ruang dan benda-benda besar kurang terfragmentasi.

Jadi di sini saya memiliki halaman 8KB luas campuran pertama, diisi dengan 7680 byte (saya telah memasukkan 30 kali 256 byte ukuran baris, jadi 30 * 256 = 7680), untuk memeriksa ukuran saya telah menjalankan ukuran periksa proc - mengembalikan hasil berikut

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

Jadi 16 KB dicadangkan untuk tabel, halaman 8 KB pertama adalah untuk halaman Root IAM, yang kedua adalah untuk halaman penyimpanan data daun yang 8KB dengan pekerjaan ~ 7,5 KB, sekarang ketika saya memasukkan baris baru dengan 256 Byte:

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

itu tidak disimpan di halaman yang sama meskipun memiliki ruang 256 byte (7680 b + 256 = 7936 yang masih lebih kecil dari 8KB), halaman data baru dibuat, tetapi baris baru itu bisa masuk pada halaman lama yang sama , mengapa SQL Server membuat halaman baru ketika itu bisa menghemat ruang dan mencari waktu membeli memasukkannya di halaman yang ada?

Catatan: hal yang sama terjadi pada heap index.

Alphas Supremum
sumber

Jawaban:

9

Baris data Anda bukan 256 byte. Masing-masing lebih seperti 263 byte. Baris data tipe data murni tetap memiliki overhead tambahan karena struktur baris data di SQL Server. Lihatlah situs ini dan baca tentang bagaimana baris data dibuat. http://aboutsqlserver.com/2013/10/15/sql-server-storage-engine-data-pages-and-data-rows/

Jadi dalam contoh Anda, Anda memiliki baris data yang memiliki 256bytes, tambahkan 2 byte untuk bit status, 2 byte untuk jumlah kolom, 2 byte untuk panjang data, dan 1 atau lebih lainnya untuk bitmap null. Itu adalah 263 * 30 = 7,890 byte. Tambahkan 263 lainnya dan Anda melebihi batas halaman 8kb yang akan memaksa halaman lain dibuat.

dfundako
sumber
Tautan yang Anda berikan membantu saya memvisualisasikan struktur halaman dengan lebih baik, saya mencari sesuatu yang mirip dengan itu tetapi tidak dapat menemukannya, Thax
Alphas Supremum
11

Meskipun benar bahwa SQL Server menggunakan 8k (8192 byte) halaman data untuk menyimpan 1 atau lebih baris, setiap halaman data memiliki beberapa overhead (96 byte), dan setiap baris memiliki beberapa overhead (setidaknya 9 byte). 8192 byte bukan murni data.

Untuk pemeriksaan yang lebih rinci tentang cara kerjanya, silakan lihat jawaban saya untuk pertanyaan DBA.SE berikut:

SUM dari DATALENGTHs tidak cocok dengan ukuran tabel dari sys.allocation_units

Dengan menggunakan informasi dalam jawaban yang tertaut itu, kita bisa mendapatkan gambaran yang lebih jelas tentang ukuran baris aktual:

  1. Header Baris = 4 byte
  2. Jumlah Kolom = 2 byte
  3. NULL Bitmap = 1 byte
  4. Info Versi ** = 14 byte (opsional, lihat catatan kaki)
  5. Overhead Per Baris Total (tidak termasuk Slot Array) = minimum 7 byte, atau 21 byte jika ada info versi
  6. Total Ukuran Baris Aktual = 263 minimum (256 data + 7 overhead), atau 277 byte (256 data + 21 overhead) jika ada info versi
  7. Menambahkan Slot Array, total ruang yang diambil per baris sebenarnya adalah 265 byte (tanpa info versi) atau 279 byte (dengan info versi).

Menggunakan DBCC PAGEkonfirmasi perhitungan saya dengan menunjukkan: Record Size 263(untuk tempdb), dan Record Size 277(untuk database yang diatur ke ALLOW_SNAPSHOT_ISOLATION ON).

Sekarang, dengan 30 baris, yaitu:

  • TANPA info versi

    30 * 263 akan memberi kita 7890 byte. Kemudian tambahkan 96 byte header halaman untuk 7986 byte yang digunakan. Terakhir, tambahkan 60 byte (2 per baris) dari array slot untuk total 8.046 byte yang digunakan pada halaman, dan 146 sisanya. Menggunakan DBCC PAGEkonfirmasi perhitungan saya dengan menunjukkan:

    • m_slotCnt 30 (mis. jumlah baris)
    • m_freeCnt 146 (yaitu jumlah byte yang tersisa di halaman)
    • m_freeData 7986 (yaitu data + header halaman - 7890 + 96 - array slot tidak diperhitungkan dalam perhitungan byte "bekas")
  • DENGAN info versi

    30 * 277 byte dengan total 8310 byte. Tapi 8310 lebih dari 8192, dan itu bahkan tidak memperhitungkan tajuk halaman 96 byte atau 2 byte per baris slot array (30 * 2 = 60 byte) yang seharusnya memberi kita hanya 8.036 byte yang dapat digunakan untuk baris.

    TAPI, bagaimana dengan 29 baris? Itu akan memberi kita 8.033 byte data (29 * 277) + 96 byte untuk header halaman + 58 byte untuk array slot (29 * 2) sama dengan 8187 byte. Dan itu akan meninggalkan halaman dengan 5 byte tersisa (8192 - 8187; tidak dapat digunakan, tentu saja). Menggunakan DBCC PAGEkonfirmasi perhitungan saya dengan menunjukkan:

    • m_slotCnt 29 (mis. jumlah baris)
    • m_freeCnt 5 (yaitu jumlah byte yang tersisa di halaman)
    • m_freeData 8129 (yaitu data + header halaman - 8033 + 96 - array slot tidak diperhitungkan dalam perhitungan byte "bekas")

Tentang Tumpukan

Tumpukan mengisi halaman data sedikit berbeda. Mereka mempertahankan perkiraan yang sangat kasar tentang jumlah ruang yang tersisa di halaman. Ketika melihat output DBCC, melihat baris untuk: PAGE HEADER: Allocation Status PFS (1:1). Anda akan melihat VALUEsesuatu menunjukkan sepanjang garis 0x60 MIXED_EXT ALLOCATED 0_PCT_FULL(ketika saya melihat tabel Clustered) atau0x64 MIXED_EXT ALLOCATED 100_PCT_FULL ketika melihat tabel Heap. Ini dievaluasi per Transaksi, jadi melakukan sisipan individual seperti tes yang dilakukan di sini dapat menunjukkan hasil yang berbeda antara tabel Clustered dan Heap. Namun, melakukan operasi DML tunggal untuk semua 30 baris akan mengisi tumpukan seperti yang diharapkan.

Namun, tidak ada detail mengenai Heaps yang secara langsung memengaruhi tes khusus ini karena kedua versi tabel tersebut cocok dengan 30 baris dengan hanya 146 byte yang tersisa. Itu tidak cukup ruang untuk baris lain, terlepas dari Clustered atau Heap.

Harap diingat bahwa tes ini agak sederhana. Menghitung ukuran aktual sebuah baris bisa menjadi sangat rumit tergantung pada berbagai faktor, seperti SPARSE:, Kompresi Data, data LOB, dll.


Untuk melihat detail halaman data, gunakan kueri berikut:

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;

** Nilai "info versi" 14 byte akan ditampilkan jika database Anda disetel ke ALLOW_SNAPSHOT_ISOLATION ONatau READ_COMMITTED_SNAPSHOT ON.

Solomon Rutzky
sumber
8060 byte per halaman tersedia untuk data pengguna, tepatnya. Data OP masih di bawah itu.
Roger Wolf
Info versi tidak ada di sana, jika tidak, 30 baris akan membutuhkan 8310 byte. Sisanya tampaknya benar.
Roger Wolf
@RogerWolf Ya, "info versi" ada di sana. Jadi ya, 30 baris memang membutuhkan 8310 byte. Itulah sebabnya ke-30 baris itu tidak, pada kenyataannya, semuanya masuk ke dalam 1 halaman karena OP dituntun untuk percaya oleh apa pun yang digunakan oleh proses pengujian OP. Tetapi tes yang salah itu. Hanya 29 baris yang pas di halaman. Saya telah mengkonfirmasi ini (menggunakan SQL Server 2012 bahkan).
Solomon Rutzky
sudahkah Anda mencoba menjalankan tes pada basis data yang tidak mendukung RCSI / tempdb? Saya dapat mereproduksi angka persis yang disediakan oleh OP.
Roger Wolf
@RogerWolf DB yang saya gunakan tidak diaktifkan RCSI, tetapi sudah diatur untuk ALLOW_SNAPSHOT_ISOLATION. Saya juga baru saja mencoba tempdbdan melihat bahwa "info versi" tidak ada, maka 30 baris cocok. Saya akan memperbarui untuk menambahkan info baru.
Solomon Rutzky
3

Struktur sebenarnya dari halaman data cukup kompleks. Meskipun secara umum dinyatakan bahwa 8060 byte per halaman tersedia untuk data pengguna, ada beberapa overhead tambahan yang tidak dihitung di mana saja yang menghasilkan perilaku ini.

Namun, Anda mungkin telah memperhatikan bahwa SQL Server benar-benar memberi Anda petunjuk bahwa baris ke-31 tidak akan masuk ke halaman. Agar baris berikutnya masuk ke halaman yang sama, avg_page_space_used_in_percentnilainya harus di bawah 100% - (100/31) = 96.774194, dan dalam kasus Anda, itu jauh di atas itu.

PS Saya yakin saya melihat penjelasan detail hingga ke byte struktur data di salah satu buku "SQL Server Internals" karya Kalen Delaney, tapi itu hampir 10 tahun yang lalu, jadi mohon maafkan saya karena tidak mengingat lebih detail. Selain itu, struktur halaman cenderung berubah dari versi ke versi.

Roger Wolf
sumber
1
Tidak. Penguning hanya ditambahkan ke baris kunci duplikat. Baris pertama dari setiap nilai kunci unik tidak termasuk pengunisi 4 byte tambahan.
Solomon Rutzky
@rutzky, ternyata Anda benar. Tidak pernah berpikir bahwa SQL Server akan mengizinkan kunci lebar variabel. Ini jelek. Efisien, ya, tapi jelek.
Roger Wolf