ไม่ได้ใช้ 512 ไบต์จากหน้าข้อมูล 8 KByte ของ SQL Server


13

ฉันสร้างตารางต่อไปนี้:

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

จากนั้นสร้างดัชนีคลัสเตอร์:

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

ต่อไปฉันเติมด้วย 30 แถวแต่ละขนาดคือ 256 ไบต์ (ตามประกาศตาราง):

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;

ตอนนี้จากข้อมูลที่ฉันอ่านใน "ชุดฝึกอบรม (สอบ 70-461): การสืบค้น Microsoft SQL Server 2012 (Itzik Ben-Gan)" หนังสือ:

SQL Server จะจัดระเบียบข้อมูลในไฟล์ข้อมูลภายในเพจ หน้าคือหน่วย 8 KB และเป็นของวัตถุเดียว; ตัวอย่างเช่นไปยังตารางหรือดัชนี หน้าเป็นหน่วยการอ่านและการเขียนที่เล็กที่สุด หน้ามีการจัดระเบียบเพิ่มเติมในขอบเขต ขอบเขตประกอบด้วยแปดหน้าต่อเนื่องกัน หน้าจากขอบเขตสามารถเป็นของวัตถุเดียวหรือหลายวัตถุ หากหน้าเป็นของวัตถุหลาย ๆ อันขอบเขตก็จะถูกเรียกว่าเป็นขอบเขตผสม หากหน้าเป็นของวัตถุเดียวขอบเขตจะเรียกว่าขอบเขตเท่ากัน SQL Server เก็บแปดหน้าแรกของวัตถุใน extents ผสม เมื่อวัตถุมีแปดหน้าเกินกว่านั้น SQL Server จะจัดสรรขอบเขตเพิ่มเติมให้กับวัตถุนี้ กับองค์กรนี้วัตถุขนาดเล็กเสียพื้นที่น้อยลงและวัตถุขนาดใหญ่มีการแยกส่วนน้อย

ดังนั้นที่นี่ฉันมีหน้าผสมขนาด 8KB หน้าแรกที่เติมด้วย 7680 ไบต์ (ฉันได้แทรกแถวขนาด 30 คูณ 256 ไบต์ดังนั้น 30 * 256 = 7680) เพื่อตรวจสอบขนาดที่ฉันเรียกใช้ตรวจสอบขนาด proc - มันส่งกลับผลลัพธ์ต่อไปนี้

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

ดังนั้น 16 KB จะถูกจองไว้สำหรับตารางหน้า 8 KB แรกสำหรับเพจรูต IAM หน้าที่สองสำหรับหน้าหน่วยเก็บข้อมูลลีฟซึ่งเป็น 8KB ที่มีการยึดครองของ ~ 7.5 KB ตอนนี้เมื่อฉันแทรกแถวใหม่ด้วย 256 ไบต์:

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

ไม่ได้เก็บไว้ในหน้าเดียวกันแม้ว่าจะมีพื้นที่ 256 ไบต์ (7680 b + 256 = 7936 ซึ่งยังคงมีขนาดเล็กกว่า 8KB) หน้าข้อมูลใหม่จะถูกสร้างขึ้น แต่แถวใหม่อาจพอดีกับหน้าเก่าเดิม เหตุใด SQL Server จึงสร้างหน้าใหม่เมื่อสามารถประหยัดพื้นที่และเวลาในการค้นหาซื้อให้แทรกในหน้าเว็บที่มีอยู่

หมายเหตุ: สิ่งเดียวกันนี้เกิดขึ้นในดัชนีฮีป

คำตอบ:


9

แถวข้อมูลของคุณไม่ใช่ 256 ไบต์ แต่ละอันมีขนาด 263 ไบต์ แถวข้อมูลของชนิดข้อมูลที่มีความยาวคงที่ล้วนมีค่าใช้จ่ายเพิ่มเติมเนื่องจากโครงสร้างของแถวข้อมูลใน SQL Server ลองดูที่เว็บไซต์นี้และอ่านเกี่ยวกับวิธีสร้างแถวข้อมูล http://aboutsqlserver.com/2013/10/15/sql-server-storage-engine-data-pages-and-data-rows/

ดังนั้นในตัวอย่างของคุณคุณมีแถวข้อมูลที่มี 256bytes เพิ่ม 2 ไบต์สำหรับบิตสถานะ 2 ไบต์สำหรับจำนวนคอลัมน์ 2 ไบต์สำหรับความยาวข้อมูลและอีก 1 หรือดังนั้นสำหรับบิตแมป null นั่นคือ 263 * 30 = 7,890bytes เพิ่มอีก 263 และคุณเกินขีด จำกัด หน้า 8kb ซึ่งจะบังคับให้สร้างหน้าอื่น


ลิงค์ที่คุณให้ไว้ช่วยให้ฉันเห็นโครงสร้างของหน้าเว็บได้ดีขึ้นฉันค้นหาสิ่งที่คล้ายกับมัน แต่ไม่สามารถหามันได้
Thax

11

ในขณะที่เป็นจริงที่ SQL Server ใช้หน้าข้อมูล 8k (8192 bytes) เพื่อจัดเก็บ 1 แถวหรือมากกว่าหน้าข้อมูลแต่ละหน้ามีค่าใช้จ่ายบางส่วน (96 ไบต์) และแต่ละแถวมีบางค่าใช้จ่าย (อย่างน้อย 9 ไบต์) 8192 ไบต์ไม่ใช่ข้อมูลล้วนๆ

สำหรับการตรวจสอบรายละเอียดเพิ่มเติมเกี่ยวกับวิธีการทำงานนี้โปรดดูคำตอบของฉันสำหรับคำถาม DBA.SE ต่อไปนี้:

SUM จาก DATALENGTHs ไม่ตรงกับขนาดตารางจาก sys.allocation_units

การใช้ข้อมูลในคำตอบที่เชื่อมโยงนั้นเราจะได้ภาพที่ชัดเจนของขนาดแถวที่แท้จริง:

  1. ส่วนหัวของแถว = 4 ไบต์
  2. จำนวนคอลัมน์ = 2 ไบต์
  3. NULL Bitmap = 1 ไบต์
  4. ข้อมูลรุ่น** = 14 ไบต์ (ตัวเลือกดูเชิงอรรถ)
  5. ค่าใช้จ่ายต่อแถวทั้งหมด (ไม่รวมอาร์เรย์ของสล็อต) = 7 ไบต์ขั้นต่ำหรือ 21 ไบต์หากมีข้อมูลเวอร์ชัน
  6. ขนาดแถวจริงโดยรวม = 263 ต่ำสุด (256 ข้อมูล + 7 ค่าโสหุ้ย) หรือ 277 ไบต์ (256 ข้อมูล + 21 ค่าโสหุ้ย) หากมีข้อมูลเวอร์ชัน
  7. การเพิ่มใน Slot Array พื้นที่ทั้งหมดที่มีต่อแถวคือ 265 ไบต์ (โดยไม่มีข้อมูลรุ่น) หรือ 279 ไบต์ (พร้อมข้อมูลรุ่น)

ใช้DBCC PAGEยืนยันการคำนวณของฉันโดยแสดง: Record Size 263(สำหรับtempdb) และRecord Size 277(สำหรับฐานข้อมูลที่ตั้งค่าไว้ALLOW_SNAPSHOT_ISOLATION ON)

ตอนนี้มี 30 แถวนั่นคือ:

  • ไม่มีข้อมูลรุ่น

    30 * 263 จะให้ 7890 ไบต์แก่เรา จากนั้นเพิ่ม 96 ไบต์ของส่วนหัวของหน้าสำหรับ 7986 ไบต์ที่ใช้ ในที่สุดเพิ่ม 60 ไบต์ (2 ต่อแถว) ของอาร์เรย์สล็อตสำหรับทั้งหมด 8046 ไบต์ที่ใช้ในหน้าและ 146 ที่เหลือ ใช้DBCC PAGEยืนยันการคำนวณของฉันโดยแสดง:

    • m_slotCnt 30 (เช่นจำนวนแถว)
    • m_freeCnt 146 (เช่นจำนวนไบต์ที่เหลือบนหน้า)
    • m_freeData 7986 (เช่นข้อมูล + ส่วนหัวของหน้า - 7890 + 96 - อาร์เรย์ของสล็อตไม่รวมอยู่ในการคำนวณไบต์ "ใช้")
  • พร้อมข้อมูลรุ่น

    30 * 277 ไบต์รวม 8310 ไบต์ แต่ 8310 นั้นมากกว่า 8192 และนั่นก็ไม่ได้คิดส่วนหัวของหน้า 96 ไบต์หรือ 2 ไบต์ต่อแถวสล็อตอาเรย์ (30 * 2 = 60 ไบต์) ซึ่งควรให้เราใช้เพียง 8036 ไบต์สำหรับแถว

    แต่แล้วประมาณ 29 แถวล่ะ นั่นจะทำให้เรามีข้อมูล 8033 ไบต์ (29 * 277) + 96 ไบต์สำหรับส่วนหัวของหน้า + 58 ไบต์สำหรับสล็อตอาร์เรย์ (29 * 2) เท่ากับ 8187 ไบต์ และนั่นจะทำให้หน้าเว็บเหลือ 5 ไบต์ (8192 - 8187; ใช้ไม่ได้แน่นอน) ใช้DBCC PAGEยืนยันการคำนวณของฉันโดยแสดง:

    • m_slotCnt 29 (เช่นจำนวนแถว)
    • m_freeCnt 5 (เช่นจำนวนไบต์ที่เหลือบนหน้า)
    • m_freeData 8129 (เช่นข้อมูล + ส่วนหัวของหน้า - 8033 + 96 - อาร์เรย์ของสล็อตไม่รวมอยู่ในการคำนวณไบต์ "ใช้")

เกี่ยวกับกอง

กองเติมหน้าข้อมูลแตกต่างกันเล็กน้อย พวกเขารักษาประมาณจำนวนพื้นที่ที่เหลือบนหน้า เมื่อมองไปที่การส่งออก DBCC PAGE HEADER: Allocation Status PFS (1:1)ดูที่แถวสำหรับ: คุณจะเห็นVALUEสิ่งที่แสดงตามบรรทัดของ0x60 MIXED_EXT ALLOCATED 0_PCT_FULL(เมื่อฉันดูที่ตารางคลัสเตอร์) หรือ0x64 MIXED_EXT ALLOCATED 100_PCT_FULLเมื่อดูที่ตารางกอง สิ่งนี้ได้รับการประเมินต่อธุรกรรมดังนั้นการทำเม็ดมีดแต่ละชิ้นเช่นการทดสอบที่ดำเนินการที่นี่อาจแสดงผลลัพธ์ที่แตกต่างกันระหว่างตารางแบบคลัสเตอร์และแบบฮีป อย่างไรก็ตามการดำเนินการ DML เดียวสำหรับทั้ง 30 แถวจะเติม heap ตามที่คาดไว้

อย่างไรก็ตามไม่มีรายละเอียดเหล่านี้เกี่ยวกับ Heaps ส่งผลโดยตรงต่อการทดสอบนี้เนื่องจากตารางทั้งสองรุ่นมีขนาด 30 แถวโดยมีเหลือเพียง 146 ไบต์เท่านั้น นั่นไม่เพียงพอสำหรับแถวอื่นโดยไม่คำนึงถึง Clustered หรือ Heap

โปรดทราบว่าการทดสอบนี้ค่อนข้างง่าย การคำนวณขนาดที่แท้จริงของแถวอาจมีความซับซ้อนมากขึ้นอยู่กับปัจจัยต่าง ๆ เช่น: SPARSEการบีบอัดข้อมูลข้อมูล LOB เป็นต้น


เมื่อต้องการดูรายละเอียดของหน้าข้อมูลใช้แบบสอบถามต่อไปนี้:

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;

** 14 ไบต์ "ข้อมูลรุ่น" ค่าจะถูกนำเสนอถ้าฐานข้อมูลของคุณถูกตั้งค่าเป็นอย่างใดอย่างหนึ่งหรือALLOW_SNAPSHOT_ISOLATION ONREAD_COMMITTED_SNAPSHOT ON


8060 ไบต์ต่อหน้าสำหรับข้อมูลผู้ใช้จะแม่นยำ ข้อมูลของ OP ยังอยู่ต่ำกว่านั้น
Roger Wolf

ไม่มีข้อมูลรุ่นมิฉะนั้น 30 แถวจะมี 8310 ไบต์ ส่วนที่เหลือดูเหมือนจะถูกต้อง
Roger Wolf

@RogerWolf ใช่มี "ข้อมูลรุ่น" อยู่ที่นั่น และใช่ใช่ 30 แถวต้องการ 8310 ไบต์ ซึ่งเป็นเหตุผลว่าทำไม 30 แถวเหล่านั้นไม่ได้เหมาะสำหรับ 1 หน้าเนื่องจาก OP จะนำไปสู่ความเชื่อโดยการทดสอบใด ๆ ก็ตามที่ OP ใช้ แต่กระบวนการทดสอบนั้นผิด มีเพียงแถว 29 แถวที่พอดีกับหน้า ฉันยืนยันเรื่องนี้แล้ว (โดยใช้ SQL Server 2012 ด้วยซ้ำ)
โซโลมอน Rutzky

คุณพยายามเรียกใช้การทดสอบของคุณบนฐานข้อมูลที่ไม่ได้เปิดใช้งาน RCSI / tempdb? ฉันสามารถทำซ้ำตัวเลขที่แน่นอนที่ให้ไว้โดย OP
Roger Wolf

@RogerWolf ดีบีผมใช้ไม่ได้ RCSI เปิดใช้งาน ALLOW_SNAPSHOT_ISOLATIONแต่ก็มีการตั้งค่า ฉันเพิ่งลองtempdbและเห็นว่า "ข้อมูลรุ่น" ไม่อยู่ที่นั่นด้วยเหตุนี้ 30 แถวพอดี ฉันจะอัปเดตเพื่อเพิ่มข้อมูลใหม่
โซโลมอน Rutzky

3

โครงสร้างที่แท้จริงของหน้าข้อมูลค่อนข้างซับซ้อน ในขณะที่มีการระบุไว้โดยทั่วไปว่า 8060 ไบต์ต่อหน้าพร้อมใช้งานสำหรับข้อมูลผู้ใช้มีค่าใช้จ่ายเพิ่มเติมบางอย่างไม่นับที่ใดก็ได้ซึ่งส่งผลให้เกิดพฤติกรรมนี้

อย่างไรก็ตามคุณอาจสังเกตเห็นว่า SQL Server ให้คำแนะนำว่าแถวที่ 31 จะไม่พอดีกับหน้า เพื่อให้แถวถัดไปพอดีกับหน้าเดียวกันavg_page_space_used_in_percentค่าควรต่ำกว่า 100% - (100/31) = 96.774194 และในกรณีของคุณมันเป็นวิธีที่เหนือกว่านั้น

PS ฉันเชื่อว่าฉันเห็นรายละเอียดลงไปถึงคำอธิบายแบบไบต์ของโครงสร้างหน้าข้อมูลในหนังสือ "SQL Server Internals" โดย Kalen Delaney แต่เมื่อเกือบ 10 ปีที่แล้วดังนั้นโปรดขอโทษด้วยที่ไม่จำรายละเอียดเพิ่มเติมอีก นอกจากนี้โครงสร้างหน้ามีแนวโน้มที่จะเปลี่ยนจากรุ่นเป็นรุ่น


1
ไม่ระบบเพิ่มตัวบ่งชี้ให้กับแถวคีย์ที่ซ้ำกันเท่านั้น แถวแรกของแต่ละค่าคีย์ที่ไม่ซ้ำกันไม่รวมตัวแยก 4 ไบต์พิเศษ
โซโลมอน Rutzky

@srutzky ดูเหมือนว่าคุณพูดถูก ไม่เคยคิดว่า SQL Server จะอนุญาตให้ใช้คีย์ความกว้างของตัวแปร นี่มันน่าเกลียด มีประสิทธิภาพใช่ แต่น่าเกลียด
Roger Wolf
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.