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


11

ฉันอยู่ภายใต้ความประทับใจว่าถ้าฉันจะรวมผลรวมDATALENGTH()ของเขตข้อมูลทั้งหมดสำหรับระเบียนทั้งหมดในตารางที่ฉันจะได้รับขนาดทั้งหมดของตาราง ฉันเข้าใจผิด

SELECT 
SUM(DATALENGTH(Field1)) + 
SUM(DATALENGTH(Field2)) + 
SUM(DATALENGTH(Field3)) TotalSizeInBytes
FROM SomeTable
WHERE X, Y, and Z are true

ฉันใช้แบบสอบถามนี้ด้านล่าง (ที่ฉันได้จากออนไลน์เพื่อรับขนาดตารางดัชนีคลัสเตอร์เท่านั้นดังนั้นจึงไม่รวมดัชนี NC) เพื่อรับขนาดของตารางเฉพาะในฐานข้อมูลของฉัน เพื่อวัตถุประสงค์ในการเรียกเก็บเงิน (เราเรียกเก็บเงินแผนกของเราตามจำนวนเนื้อที่ที่พวกเขาใช้) ฉันต้องทราบว่าแต่ละแผนกใช้พื้นที่เท่าใดในตารางนี้ ฉันมีแบบสอบถามที่ระบุแต่ละกลุ่มภายในตาราง ฉันแค่ต้องคิดออกว่าแต่ละกลุ่มจะมีพื้นที่ว่างเท่าใด

ช่องว่างต่อแถวอาจแกว่งอย่างมากเนื่องจากVARCHAR(MAX)ฟิลด์ในตารางดังนั้นฉันจึงไม่สามารถใช้ขนาดเฉลี่ย * อัตราส่วนของแถวสำหรับแผนก เมื่อฉันใช้DATALENGTH()วิธีการที่อธิบายข้างต้นฉันได้รับเพียง 85% ของพื้นที่ทั้งหมดที่ใช้ในแบบสอบถามด้านล่าง คิด?

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

มีคนแนะนำว่าฉันสร้างดัชนีที่ถูกกรองสำหรับแต่ละแผนกหรือพาร์ติชันตารางดังนั้นฉันสามารถสอบถามพื้นที่ที่ใช้ต่อดัชนีได้โดยตรง ดัชนีที่กรองได้สามารถสร้างโดยทางโปรแกรม (และลดลงอีกครั้งในช่วงเวลาการบำรุงรักษาหรือเมื่อฉันต้องทำการเรียกเก็บเงินเป็นระยะ) แทนที่จะใช้พื้นที่ตลอดเวลา (พาร์ติชันจะดีกว่าในแง่นี้)

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

เกี่ยวกับดัชนี nonclustered ในตารางนี้: ถ้าฉันสามารถหาขนาดของดัชนี NC ได้มันจะดีมาก อย่างไรก็ตามดัชนี NC มีขนาด <1% ของดัชนีคลัสเตอร์ดังนั้นเราจึงไม่รวมสิ่งเหล่านั้น อย่างไรก็ตามเราจะรวมดัชนี NC อย่างไร ฉันไม่สามารถรับขนาดที่ถูกต้องสำหรับดัชนีแบบคลัสเตอร์ :)


ดังนั้นในสาระสำคัญคุณมีสองคำถาม: (1) ทำไมผลรวมของความยาวแถวไม่ตรงกับการบัญชีของข้อมูลเมตาของขนาดของตารางทั้งหมด? คำตอบด้านล่างระบุว่าอย่างน้อยก็ในบางส่วน (และสามารถผันผวนได้ต่อการเปิดตัวและต่อคุณสมบัติตัวอย่างเช่นการบีบอัด, คอลัมน์, ฯลฯ ) และที่สำคัญยิ่งกว่า: (2) คุณสามารถกำหนดพื้นที่จริงที่ใช้จริงต่อแผนกได้อย่างไร ฉันไม่ทราบว่าคุณสามารถทำสิ่งนั้นได้อย่างถูกต้องเพราะสำหรับข้อมูลบางส่วนที่คิดในคำตอบจึงไม่มีวิธีที่จะบอกแผนกที่เป็นของมัน
Aaron Bertrand

ฉันไม่คิดว่าปัญหาคือคุณไม่มีขนาดที่ถูกต้องสำหรับดัชนีคลัสเตอร์ - เมทาดาทาจะบอกคุณอย่างแม่นยำด้วยความแม่นยำว่าดัชนีของคุณใช้พื้นที่เท่าใด ข้อมูลเมตาใดที่ไม่ได้ออกแบบมาเพื่อบอกคุณอย่างน้อยก็ให้การออกแบบ / โครงสร้างปัจจุบันของคุณว่าข้อมูลเชื่อมโยงกับแต่ละแผนกมากน้อยเพียงใด
Aaron Bertrand

คำตอบ:


19

                          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.

ข้อมูลไม่ได้เป็นเพียงสิ่งเดียวที่ใช้พื้นที่ในหน้าข้อมูล 8k:

  • มีพื้นที่สงวนไว้ คุณได้รับอนุญาตให้ใช้ 8060 จาก 8192 ไบต์ (นั่นคือ 132 ไบต์ที่ไม่เคยเป็นของคุณตั้งแต่แรก):

    • ส่วนหัวของหน้า: นี่คือ 96 ไบต์
    • Slot array: นี่คือ 2 ไบต์ต่อแถวและบ่งบอกถึงการชดเชยของที่แต่ละแถวเริ่มต้นในหน้า ขนาดของอาร์เรย์นี้ไม่ได้ จำกัด อยู่ที่ 36 ไบต์ที่เหลือ (132 - 96 = 36) มิฉะนั้นคุณจะถูก จำกัด อย่างมีประสิทธิภาพเพียงวาง 18 แถวสูงสุดในหน้าข้อมูล ซึ่งหมายความว่าแต่ละแถวมีขนาดใหญ่กว่าที่คุณคิด 2 ไบต์ ค่านี้ไม่รวมอยู่ใน "ขนาดบันทึก" ตามที่รายงานโดยDBCC PAGEซึ่งเป็นสาเหตุที่แยกเก็บไว้ที่นี่แทนที่จะรวมอยู่ในข้อมูลต่อแถวด้านล่าง
    • ข้อมูลเมตาต่อแถว (รวมถึง แต่ไม่ จำกัด เฉพาะ):
      • ขนาดแตกต่างกันไปขึ้นอยู่กับคำจำกัดความของตาราง (เช่นจำนวนคอลัมน์ความยาวผันแปรหรือความยาวคงที่ ฯลฯ ) ข้อมูลที่นำมาจากความคิดเห็นของ @ PaulWhite และ @ Aaron ที่สามารถพบได้ในการสนทนาที่เกี่ยวข้องกับคำตอบและการทดสอบนี้
      • ส่วนหัวแถว: 4 ไบต์, 2 ในนั้นแสดงถึงชนิดของเรกคอร์ด, และอีกสองอันเป็นออฟเซ็ตเป็น NULL Bitmap
      • จำนวนคอลัมน์: 2 ไบต์
      • โมฆะ Bitmap: NULLคอลัมน์ที่มีอยู่ในปัจจุบัน 1 ไบต์ต่อ 8 คอลัมน์แต่ละชุด และสำหรับคอลัมน์ทั้งหมดแม้แต่รายการNOT NULLเดียว ดังนั้นอย่างน้อย 1 ไบต์
      • อาร์เรย์ออฟเซ็ตคอลัมน์ความยาวผันแปร: ขั้นต่ำ 4 ไบต์ 2 ไบต์เพื่อเก็บจำนวนคอลัมน์ที่มีความยาวผันแปรจากนั้น 2 ไบต์ต่อคอลัมน์ที่มีความยาวผันแปรแต่ละคอลัมน์เพื่อเก็บออฟเซ็ตให้ตรงกับที่เริ่ม
      • ข้อมูลการกำหนดรุ่น: 14 ไบต์ (สิ่งนี้จะปรากฏหากฐานข้อมูลของคุณถูกตั้งค่าเป็นALLOW_SNAPSHOT_ISOLATION ONหรือREAD_COMMITTED_SNAPSHOT ON)
    • โปรดดูคำถามและคำตอบต่อไปนี้สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับเรื่องนี้: อาเรย์สล็อตและขนาดหน้าทั้งหมด
    • โปรดดูโพสต์บล็อกต่อไปนี้จาก Paul Randall ซึ่งมีรายละเอียดที่น่าสนใจหลายประการเกี่ยวกับวิธีจัดวางหน้าข้อมูล: Poking with DBCC PAGE (ตอนที่ 1 ของ)
  • พอยน์เตอร์ LOB สำหรับข้อมูลที่ไม่ได้จัดเก็บไว้ในแถว นั่นจะคิดเป็นDATALENGTH+ pointer_size แต่นี่ไม่ใช่ขนาดมาตรฐาน โปรดดูโพสต์บล็อกต่อไปนี้สำหรับรายละเอียดเกี่ยวกับหัวข้อที่ซับซ้อนนี้ขนาดของ LOB Pointer สำหรับ (MAX) ประเภทคืออะไรเช่น Varchar, Varbinary, Etc? . ระหว่างการโพสต์ที่เชื่อมโยงกับการทดสอบเพิ่มเติมที่ฉันได้ทำไว้กฎ (ค่าเริ่มต้น) ควรเป็นดังนี้:

    • มรดก / เลิกลอบประเภทที่ไม่มีใครควรจะใช้อีกต่อไปเป็นของ SQL Server 2005 ( TEXT, NTEXTและIMAGE):
      • โดยค่าเริ่มต้นให้เก็บข้อมูลไว้ในหน้า LOB และใช้ตัวชี้ 16 ไบต์กับที่จัดเก็บ LOB เสมอ
      • หากใช้ sp_tableoptionเพื่อตั้งค่าtext in rowตัวเลือกจากนั้น:
        • หากมีพื้นที่บนหน้าเพื่อเก็บค่าและค่าไม่เกินขนาดสูงสุดในแถว (ช่วงที่กำหนดได้ระหว่าง 24 - 7000 ไบต์โดยมีค่าเริ่มต้นเท่ากับ 256) จากนั้นจะถูกเก็บไว้ในแถว
        • มิฉะนั้นจะเป็นตัวชี้ 16 ไบต์
    • สำหรับชนิดลอบใหม่แนะนำใน SQL Server 2005 ( VARCHAR(MAX), NVARCHAR(MAX)และVARBINARY(MAX)):
      • โดยค่าเริ่มต้น:
        • หากค่าไม่เกิน 8000 ไบต์และมีพื้นที่ในหน้าเอกสารจะถูกเก็บไว้ในแถว
        • Inline Root - สำหรับข้อมูลระหว่าง 8001 ถึง 40,000 (จริง ๆ 42,000) ไบต์อนุญาตให้มีพื้นที่จะมี 1 ถึง 5 ตัวชี้ (24 - 72 ไบต์) ในแถวที่ชี้ไปยังหน้า LOB โดยตรง 24 ไบต์สำหรับหน้า 8K LOB เริ่มต้นและ 12 ไบต์ต่อหน้า 8k เพิ่มเติมแต่ละหน้าสำหรับหน้าขนาด 8k ขึ้นอีกสี่หน้า
        • TEXT_TREE - สำหรับข้อมูลที่มีมากกว่า 42,000 ไบต์หรือหากตัวชี้ 1 ถึง 5 ไม่พอดีในแถวจากนั้นจะมีเพียง 24 ไบต์ตัวชี้ไปยังหน้าเริ่มต้นของรายการตัวชี้ไปยังหน้า LOB (เช่น "text_tree" "หน้า)
      • หากใช้ sp_tableoptionเพื่อตั้งค่าlarge value types out of rowตัวเลือกจากนั้นใช้ตัวชี้ 16 ไบต์เป็นที่เก็บข้อมูล LOB
    • ฉันพูดว่ากฎ "default" เพราะฉันไม่ได้ทดสอบค่า in-row กับผลกระทบของคุณสมบัติบางอย่างเช่นการบีบอัดข้อมูล, การเข้ารหัสระดับคอลัมน์, การเข้ารหัสข้อมูลแบบโปร่งใส, เข้ารหัสตลอดเวลา ฯลฯ
  • LOB หน้าโอเวอร์โฟลว์: หากค่าคือ 10k แสดงว่าต้องมีโอเวอร์โฟลว์หน้าเต็ม 1k เต็ม 8k จากนั้นเป็นส่วนหนึ่งของหน้า 2 หากไม่มีข้อมูลอื่นสามารถใช้พื้นที่ที่เหลืออยู่ (หรือแม้กระทั่งได้รับอนุญาตฉันไม่แน่ใจในกฎนั้น) จากนั้นคุณมีพื้นที่ "สูญเปล่า" ประมาณ 6kb ของข้อมูล LOB ล้นอันที่สอง

  • พื้นที่ที่ไม่ได้ใช้: หน้าข้อมูล 8k เป็นเพียง: 8192 ไบต์ ขนาดไม่แตกต่างกัน อย่างไรก็ตามข้อมูลและ meta-data ที่วางอยู่บนนั้นไม่ได้พอดีกับ 8192 ไบต์ทั้งหมด และแถวไม่สามารถแบ่งออกเป็นหน้าข้อมูลหลาย ๆ หน้าได้ ดังนั้นหากคุณมี 100 ไบต์ที่เหลือ แต่ไม่มีแถว (หรือไม่มีแถวที่จะพอดีกับตำแหน่งนั้นขึ้นอยู่กับปัจจัยหลายประการ) หน้าข้อมูลจะยังคงมีอยู่ 8192 ไบต์และแบบสอบถามที่ 2 ของคุณนับเฉพาะจำนวน หน้าข้อมูล คุณสามารถหาค่านี้ได้ในสองแห่ง (โปรดจำไว้ว่าบางส่วนของค่านี้คือจำนวนของพื้นที่สงวนนั้น):

    • DBCC PAGE( db_name, file_id, page_id ) WITH TABLERESULTS;ค้นหาParentObject= "PAGE HEADER:" และField= "m_freeCnt" Valueฟิลด์เป็นจำนวนไบต์ที่ไม่ได้ใช้
    • 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;นี่เป็นค่าเดียวกับที่รายงานโดย "m_freeCnt" สิ่งนี้ง่ายกว่า DBCC เนื่องจากสามารถรับได้หลายหน้า แต่ยังต้องการให้มีการอ่านหน้าต่างๆลงในบัฟเฟอร์พูลตั้งแต่แรก
  • พื้นที่ที่สงวนไว้โดยFILLFACTOR<100 หน้าที่สร้างขึ้นใหม่ไม่เคารพการFILLFACTORตั้งค่า แต่การทำ REBUILD จะเป็นการจองพื้นที่นั้นในแต่ละหน้าข้อมูล แนวคิดเบื้องหลังพื้นที่สงวนคือมันจะถูกใช้โดยส่วนแทรกแบบไม่ต่อเนื่องและ / หรือการปรับปรุงที่ขยายขนาดของแถวบนหน้าไปแล้วเนื่องจากคอลัมน์ความยาวผันแปรถูกอัปเดตด้วยข้อมูลเพิ่มเติมเล็กน้อย (แต่ไม่เพียงพอที่จะทำให้ หน้าแยก) แต่คุณสามารถจองพื้นที่บนหน้าข้อมูลที่ไม่เคยได้รับแถวใหม่ตามธรรมชาติและไม่เคยมีการปรับปรุงแถวที่มีอยู่หรืออย่างน้อยก็ไม่ได้ปรับปรุงในวิธีที่จะเพิ่มขนาดของแถว

  • การแบ่งหน้า (การแยกส่วน): ต้องการเพิ่มแถวไปยังตำแหน่งที่ไม่มีที่ว่างสำหรับแถวจะทำให้เกิดการแบ่งหน้า ในกรณีนี้ประมาณ 50% ของข้อมูลที่มีอยู่จะถูกย้ายไปยังหน้าใหม่และแถวใหม่จะถูกเพิ่มไปยังหนึ่งใน 2 หน้า แต่ตอนนี้คุณมีพื้นที่ว่างเพิ่มขึ้นเล็กน้อยที่ไม่ได้ถูกDATALENGTHคำนวณ

  • ทำเครื่องหมายแถวเพื่อลบ เมื่อคุณลบแถวพวกเขาจะไม่ถูกลบออกทันทีจากหน้าข้อมูล หากไม่สามารถลบออกได้ในทันทีพวกเขาจะถูก "ทำเครื่องหมายเพื่อความตาย" (อ้างอิงสตีเว่นซีกัล) และจะถูกลบออกในภายหลังโดยกระบวนการล้างผี (ฉันเชื่อว่าเป็นชื่อ) อย่างไรก็ตามสิ่งเหล่านี้อาจไม่เกี่ยวข้องกับคำถามนี้

  • หน้าผี ไม่แน่ใจว่าเป็นคำที่เหมาะสมหรือไม่ แต่บางครั้งหน้าข้อมูลจะไม่ถูกลบจนกว่าจะสร้าง REBUILD ของ Clustered Index เสร็จแล้ว ที่จะบัญชีสำหรับหน้ามากกว่าDATALENGTHจะเพิ่มขึ้น สิ่งนี้โดยทั่วไปไม่ควรเกิดขึ้น แต่ฉันพบเจอครั้งหนึ่งเมื่อหลายปีก่อน

  • คอลัมน์ SPARSE: คอลัมน์ที่กระจัดกระจายช่วยประหยัดพื้นที่ (ส่วนใหญ่สำหรับประเภทข้อมูลที่มีความยาวคงที่) ในตารางที่% ขนาดใหญ่ของแถวNULLสำหรับคอลัมน์หนึ่งคอลัมน์หรือมากกว่า SPARSEตัวเลือกที่จะทำให้NULLประเภทค่าขึ้น 0 ไบต์ (แทนของจำนวนเงินความยาวคงปกติเช่น 4 ไบต์สำหรับINT) แต่ไม่เป็นโมฆะค่าแต่ละใช้เวลาเพิ่มอีก 4 ไบต์สำหรับประเภทความยาวคงที่และจำนวนตัวแปร ประเภทความยาวผันแปรได้ ปัญหาที่นี่คือที่DATALENGTHไม่รวม 4 ไบต์พิเศษสำหรับค่าที่ไม่ใช่ NULL ในคอลัมน์ SPARSE ดังนั้นต้องเพิ่ม 4 ไบต์เหล่านั้นกลับมาคุณสามารถตรวจสอบเพื่อดูว่ามีSPARSEคอลัมน์ใด ๆผ่าน:

    SELECT OBJECT_SCHEMA_NAME(sc.[object_id]) AS [SchemaName],
           OBJECT_NAME(sc.[object_id]) AS [TableName],
           sc.name AS [ColumnName]
    FROM   sys.columns sc
    WHERE  sc.is_sparse = 1;

    จากนั้นสำหรับแต่ละSPARSEคอลัมน์ให้ปรับปรุงคิวรีดั้งเดิมที่จะใช้:

    SUM(DATALENGTH(FieldN) + 4)

    โปรดทราบว่าการคำนวณข้างต้นเพื่อเพิ่มในมาตรฐาน 4 ไบต์นั้นค่อนข้างง่ายเพราะใช้งานได้กับประเภทที่มีความยาวคงที่เท่านั้น และมี meta-data เพิ่มเติมต่อแถว (จากสิ่งที่ฉันสามารถบอกได้จนถึงตอนนี้) ที่ลดพื้นที่ว่างสำหรับข้อมูลเพียงแค่มีคอลัมน์ SPARSE อย่างน้อยหนึ่งคอลัมน์ สำหรับรายละเอียดเพิ่มเติมกรุณาดูที่หน้า MSDN สำหรับคอลัมน์ใช้เบาบาง

  • หน้าดัชนีและอื่น ๆ (เช่น IAM, PFS, GAM, SGAM และอื่น ๆ ) หน้าเหล่านี้ไม่ใช่หน้า "ข้อมูล" ในแง่ของข้อมูลผู้ใช้ สิ่งเหล่านี้จะขยายขนาดรวมของตาราง หากใช้ SQL Server 2012 หรือใหม่กว่าคุณสามารถใช้sys.dm_db_database_page_allocationsDynamic Management Function (DMF) เพื่อดูประเภทเพจ (SQL Server เวอร์ชันก่อนหน้านี้สามารถใช้ได้DBCC IND(0, N'dbo.table_name', 0);):

    SELECT *
    FROM   sys.dm_db_database_page_allocations(
                   DB_ID(),
                   OBJECT_ID(N'dbo.table_name'),
                   1,
                   NULL,
                   N'DETAILED'
                  )
    WHERE  page_type = 1; -- DATA_PAGE

    จะไม่รายงานDBCC INDหรือหน้าsys.dm_db_database_page_allocations(ที่มีส่วนคำสั่ง WHERE) และหน้าDBCC INDจะรายงานอย่างน้อยหนึ่งหน้า IAM

  • DATA_COMPRESSION: หากคุณมีROWหรือPAGEเปิดใช้งานการบีบอัดในดัชนีแบบกลุ่มหรือส่วนใหญ่คุณสามารถลืมสิ่งที่ได้กล่าวถึงไปแล้วส่วนใหญ่ ส่วนหัวของหน้า 96 ไบต์, 2 อาร์เรย์ไบต์ต่อแถวและ 14 บิตต่อแถวข้อมูลการเรียงลำดับยังคงมีอยู่ แต่การแสดงทางกายภาพของข้อมูลจะซับซ้อนมาก (มากกว่าที่ได้กล่าวไว้แล้วเมื่อการบีบอัด ไม่ได้ใช้งาน) ตัวอย่างเช่นเมื่อใช้การบีบอัดแถว SQL Server จะพยายามใช้คอนเทนเนอร์ที่เล็กที่สุดเท่าที่จะเป็นไปได้เพื่อให้พอดีกับแต่ละคอลัมน์ต่อแต่ละแถว ดังนั้นหากคุณมีBIGINTคอลัมน์ที่จะเป็นอย่างอื่น (สมมติSPARSEว่าไม่ได้เปิดใช้งาน) จะใช้เวลา 8 ไบต์เสมอหากค่าอยู่ระหว่าง -128 ถึง 127 (เช่นจำนวนเต็ม 8 บิตที่ลงนามแล้ว) มันจะใช้เพียง 1 ไบต์และถ้า ค่าอาจจะพอดีกับSMALLINTมันจะใช้เวลา 2 ไบต์เท่านั้น ชนิดจำนวนเต็มที่เป็นNULLหรือไม่0ใช้พื้นที่และระบุเพียงว่าเป็นNULLหรือ "ว่างเปล่า" (เช่น0) ในการแม็พอาร์เรย์ออกคอลัมน์ และมีกฎอื่น ๆ อีกมากมาย ข้อมูลที่มี Unicode ( NCHAR, NVARCHAR(1 - 4000)แต่ไม่ได้ NVARCHAR(MAX)แม้ว่าเก็บไว้ในแถว)? Unicode บีบอัดถูกเพิ่มเข้ามาใน SQL Server 2008 R2 แต่มีวิธีที่จะคาดการณ์ผลของการ "บีบอัด" ในทุกสถานการณ์โดยไม่ต้องทำการบีบอัดที่เกิดขึ้นจริงได้รับความซับซ้อนของไม่มีกฎ

ดังนั้นคำถามที่สองของคุณในขณะที่แม่นยำยิ่งขึ้นในแง่ของพื้นที่ทางกายภาพทั้งหมดที่ใช้บนดิสก์นั้นจะมีความถูกต้องจริง ๆ เท่านั้นเมื่อทำREBUILDดัชนี Clustered และหลังจากนั้นคุณยังต้องคำนึงถึงการFILLFACTORตั้งค่าที่ต่ำกว่า 100 และถึงแม้จะมีส่วนหัวของหน้าอยู่เสมอและมักจะมีเนื้อที่ "สูญเปล่า" ที่เพียงพอซึ่งไม่สามารถเติมได้เนื่องจากมีขนาดเล็กเกินไปที่จะพอดีกับแถวในแถวนี้ ตารางหรืออย่างน้อยแถวที่มีเหตุผลควรเข้าไปในช่องนั้น

เกี่ยวกับความถูกต้องของการสืบค้นครั้งที่ 2 ในการพิจารณา "การใช้ข้อมูล" ดูเหมือนว่ายุติธรรมที่สุดในการสำรองข้อมูลส่วนหัวของเพจเนื่องจากไม่ใช่การใช้ข้อมูล: เป็นค่าใช้จ่ายทางธุรกิจ หากมี 1 แถวในหน้าข้อมูลและแถวนั้นมีเพียง a TINYINTดังนั้น 1 ไบต์นั้นยังจำเป็นต้องมีหน้าข้อมูลอยู่และด้วยเหตุนี้จึงมี 96 ไบต์ของส่วนหัว ควรคิดค่าบริการ 1 แผนกสำหรับหน้าข้อมูลทั้งหมดหรือไม่ หากหน้าข้อมูลนั้นเต็มไปด้วยแผนก # 2 พวกเขาจะแบ่งค่าใช้จ่าย "ค่าใช้จ่าย" หรือจ่ายตามสัดส่วนอย่างเท่าเทียมกันหรือไม่ ดูเหมือนจะง่ายที่สุดในการสำรอง ในกรณีนี้การใช้ค่า8ทวีคูณเทียบกับnumber of pagesนั้นสูงเกินไป เกี่ยวกับ:

-- 8192 byte data page - 96 byte header = 8096 (approx) usable bytes.
SELECT 8060.0 / 1024 -- 7.906250

ดังนั้นให้ใช้สิ่งที่ชอบ:

(SUM(a.total_pages) * 7.91) / 1024 AS [TotalSpaceMB]

สำหรับการคำนวณทั้งหมดกับคอลัมน์ "number_of_pages"

และเนื่องจากการใช้DATALENGTHต่อแต่ละฟิลด์ไม่สามารถส่งคืนเมตาดาต้าต่อแถวซึ่งควรเพิ่มลงในคิวรีต่อตารางของคุณซึ่งคุณจะได้รับDATALENGTHข้อมูลแต่ละฟิลด์โดยกรองแต่ละแผนก ":

  • ประเภทเร็กคอร์ดและออฟเซ็ตเป็น NULL Bitmap: 4 ไบต์
  • จำนวนคอลัมน์: 2 ไบต์
  • Array ของช่อง: 2 ไบต์ (ไม่รวมอยู่ใน "ขนาดบันทึก" แต่ยังต้องมีการบัญชี)
  • NULL Bitmap: 1 ไบต์ต่อทุกๆ 8 คอลัมน์ (สำหรับคอลัมน์ทั้งหมด )
  • การกำหนดเวอร์ชันของแถว: 14 ไบต์ (หากฐานข้อมูลมีALLOW_SNAPSHOT_ISOLATIONหรือREAD_COMMITTED_SNAPSHOTตั้งค่าเป็นON)
  • คอลัมน์ความยาวแปรผันอาร์เรย์ออฟเซ็ต: 0 ไบต์หากคอลัมน์ทั้งหมดมีความยาวคงที่ หากคอลัมน์ใดมีความยาวผันแปรดังนั้น 2 ไบต์และบวก 2 ไบต์ต่อคอลัมน์ความยาวแปรผันเท่านั้น
  • พอยน์เตอร์ LOB: ส่วนนี้ไม่แน่ชัดเนื่องจากไม่มีตัวชี้หากค่าเป็นNULLและถ้าค่าพอดีกับแถวมันจะเล็กกว่าหรือใหญ่กว่าตัวชี้มากและถ้าเก็บค่าไว้ แถวจากนั้นขนาดของตัวชี้อาจขึ้นอยู่กับจำนวนข้อมูลที่มี อย่างไรก็ตามเนื่องจากเราต้องการประมาณการ (เช่น "swag") ดูเหมือนว่า 24 ไบต์เป็นค่าที่ดีในการใช้ (เช่นเดียวกับอื่น ๆ ;-) นี่คือแต่ละMAXฟิลด์

ดังนั้นให้ใช้สิ่งที่ชอบ:

  • โดยทั่วไป (ส่วนหัวแถว + จำนวนคอลัมน์ + อาร์เรย์สล็อต + บิตแมป NULL):

    ([RowCount] * (( 4 + 2 + 2 + (1 + (({NumColumns} - 1) / 8) ))
  • โดยทั่วไป (ตรวจสอบอัตโนมัติหากมี "ข้อมูลรุ่น" อยู่):

    + (SELECT CASE WHEN snapshot_isolation_state = 1 OR is_read_committed_snapshot_on = 1
                     THEN 14 ELSE 0 END FROM sys.databases WHERE [database_id] = DB_ID())
  • หากมีคอลัมน์ที่มีความยาวผันแปรให้เพิ่ม:

    + 2 + (2 * {NumVariableLengthColumns})
  • หากมีMAXคอลัมน์ / LOB ใด ๆให้เพิ่ม:

    + (24 * {NumLobColumns})
  • โดยทั่วไป:

    )) AS [MetaDataBytes]

สิ่งนี้ไม่ถูกต้องและอีกครั้งจะไม่ทำงานหากคุณเปิดใช้งานแถวหรือการบีบอัดหน้าในดัชนีกองหรือกลุ่ม แต่ควรให้คุณเข้าใกล้อย่างแน่นอน


อัปเดตเกี่ยวกับความแตกต่างลึกลับ 15%

เรา (รวมถึงตัวเอง) มุ่งเน้นที่การคิดเกี่ยวกับวิธีการจัดวางหน้าข้อมูลและวิธีการที่DATALENGTHบัญชีสำหรับสิ่งที่เราไม่ได้ใช้เวลามากในการตรวจสอบแบบสอบถามที่ 2 ฉันเรียกใช้คิวรีนั้นกับตารางเดียวแล้วเปรียบเทียบค่าเหล่านั้นกับสิ่งที่ถูกรายงานโดยsys.dm_db_database_page_allocationsพวกเขาไม่ใช่ค่าเดียวกันสำหรับจำนวนหน้า เกี่ยวกับลางสังหรณ์ผมเอาฟังก์ชั่นรวมและGROUP BYและแทนที่รายการที่มีSELECT a.*, '---' AS [---], p.*และจากนั้นมันก็ชัดเจน: ผู้คนจะต้องระมัดระวังในที่ที่มืดมัวเข้าด้วยกันพวกเขาได้รับข้อมูลและสคริปต์จาก ;-) แบบสอบถามที่ 2 ที่โพสต์ในคำถามนั้นไม่ถูกต้องโดยเฉพาะอย่างยิ่งสำหรับคำถามนี้โดยเฉพาะ

  • ปัญหาเล็ก ๆ น้อย ๆ : นอกจากจะไม่ค่อยสมเหตุสมผลGROUP BY rows(และไม่มีคอลัมน์นั้นในฟังก์ชั่นรวม) การเข้าร่วมระหว่างsys.allocation_unitsและsys.partitionsไม่ถูกต้องทางเทคนิค หน่วยการจัดสรรมี 3 ประเภทและหนึ่งในนั้นควรรวมเข้ากับฟิลด์อื่น บ่อยครั้งpartition_idและhobt_idเหมือนกันดังนั้นอาจไม่มีปัญหา แต่บางครั้งทั้งสองฟิลด์มีค่าต่างกัน

  • ปัญหาที่สำคัญ: แบบสอบถามใช้used_pagesเขตข้อมูล ฟิลด์นั้นครอบคลุมหน้าเว็บทุกประเภท: ข้อมูล, ดัชนี, IAM, ฯลฯ , tc data_pagesมีอีกข้อมูลที่เหมาะสมต่อการใช้งานเมื่อเกี่ยวข้องกับเฉพาะข้อมูลที่เกิดขึ้นจริงคือ

ฉันปรับคำถามที่ 2 ในคำถามโดยคำนึงถึงรายการข้างต้นและใช้ขนาดหน้าข้อมูลที่สำรองส่วนหัวของหน้า ฉันยังเอาออกสองร่วมที่ไม่จำเป็น: sys.schemas(แทนที่ด้วยการเรียกร้องให้SCHEMA_NAME()) และsys.indexes(ดัชนีคลัสเตอร์อยู่เสมอindex_id = 1และเรามีindex_idในsys.partitions)

SELECT  SCHEMA_NAME(st.[schema_id]) AS [SchemaName],
        st.[name] AS [TableName],
        SUM(sp.[rows]) AS [RowCount],
        (SUM(sau.[total_pages]) * 8.0) / 1024 AS [TotalSpaceMB],
        (SUM(CASE sau.[type]
           WHEN 1 THEN sau.[data_pages]
           ELSE (sau.[used_pages] - 1) -- back out the IAM page
         END) * 7.91) / 1024 AS [TotalActualDataMB]
FROM        sys.tables st
INNER JOIN  sys.partitions sp
        ON  sp.[object_id] = st.[object_id]
INNER JOIN  sys.allocation_units sau
        ON  (   sau.[type] = 1
            AND sau.[container_id] = sp.[partition_id]) -- IN_ROW_DATA
        OR  (   sau.[type] = 2
            AND sau.[container_id] = sp.[hobt_id]) -- LOB_DATA
        OR  (   sau.[type] = 3
            AND sau.[container_id] = sp.[partition_id]) -- ROW_OVERFLOW_DATA
WHERE       st.is_ms_shipped = 0
--AND         sp.[object_id] = OBJECT_ID(N'dbo.table_name')
AND         sp.[index_id] < 2 -- 1 = Clustered Index; 0 = Heap
GROUP BY    SCHEMA_NAME(st.[schema_id]), st.[name]
ORDER BY    [TotalSpaceMB] DESC;

ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
พอลไวท์ 9

แม้ว่าข้อความค้นหาที่อัปเดตที่คุณระบุไว้สำหรับการค้นหาครั้งที่สองนั้นจะออกไปไกลกว่านี้ (ในทิศทางอื่นตอนนี้ :)) ฉันก็โอเคกับคำตอบนี้ นี่เป็นถั่วที่ยากมากที่จะถอดรหัสและสำหรับสิ่งที่คุ้มค่าฉันดีใจแม้กับผู้เชี่ยวชาญที่ช่วยฉันฉันก็ยังไม่สามารถหาเหตุผลที่แน่นอนได้ว่าทั้งสองวิธีไม่ตรงกัน ฉันจะใช้วิธีการในคำตอบอื่น ๆ เพื่อคาดการณ์ ฉันหวังว่าฉันจะสามารถโหวตใช่สำหรับคำตอบทั้งสองนี้ แต่ @srutzky ช่วยด้วยเหตุผลทั้งหมดว่าทำไมทั้งสองจึงถูกปิด
Chris Woods

6

บางทีนี่อาจเป็นคำตอบของกรันจ์ แต่นี่คือสิ่งที่ฉันจะทำ

ดังนั้น DATALENGTH คิดเป็นเพียง 86% ของจำนวนทั้งหมด มันยังคงแบ่งตัวแทนมาก ค่าโสหุ้ยในคำตอบที่ดีจาก srutzky ควรมีการแบ่งที่ค่อนข้างสวย

ฉันจะใช้การค้นหาที่สองของคุณ (หน้า) สำหรับผลรวม และใช้ช่วงแรก (ข้อมูล) สำหรับการจัดสรรการแยก มีการปันส่วนต้นทุนจำนวนมากโดยใช้การทำให้เป็นมาตรฐาน

และคุณต้องพิจารณาคำตอบที่ใกล้ชิดมากขึ้นว่าจะเพิ่มต้นทุนดังนั้นแม้แผนกที่แยกออกจากการแยกอาจยังคงจ่ายมากขึ้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.