了解sp_spaceused值:reserved和index_size

时间:2017-03-18 22:51:05

标签: sql-server allocation

我正在运行以下简单脚本:

create table MyTable (filler char(10))
go

insert into MyTable (filler) values ('a')
go 1

exec sp_spaceused MyTable
go

drop table MyTable
go

并获得以下结果:

rows   reserved   data   index_size    unused
------ ---------- ------ -----------   -------
1      72 KB      8 KB   8 KB          56 KB

我的问题:

  1. 为什么要保留72 KB?

  2. 如果表甚至没有编入索引,为什么index_size为8 KB?

  3. 编辑:

    我想添加一个跟进: 稍微更改脚本时:

    create table MyTable (filler char(69))
    go
    
    insert into MyTable (filler) values ('a')
    go 100
    

    我明白了:

    rows   reserved   data   index_size    unused
    ------ ---------- ------ -----------   -------
    100    72 KB      16 KB   8 KB          48 KB
    

    请注意,将filler的大小定义为68个字节(并插入100行)仍然会提供8KB作为data值(我们可以继续并将其设置为148个字节,这将是导致另外8KB增量,即到24KB)。

    你能帮我打破一下这个计算吗?如果(显然)只使用了6,900个字节,那么8KB的添加原因是什么?

    编辑#2:编辑#2: 这是DBCC PAGE

    的结果
    PAGE: (1:4392)
    
    
    BUFFER:
    
    
    BUF @0x00000000061A78C0
    
    bpage = 0x00000001EF3A8000          bhash = 0x0000000000000000          bpageno = (1:4392)
    bdbid = 6                           breferences = 0                     bcputicks = 0
    bsampleCount = 0                    bUse1 = 18482                       bstat = 0x9
    blog = 0x15ab215a                   bnext = 0x0000000000000000          bDirtyContext = 0x0000000000000000
    bstat2 = 0x0                        
    
    PAGE HEADER:
    
    
    Page @0x00000001EF3A8000
    
    m_pageId = (1:4392)                 m_headerVersion = 1                 m_type = 1
    m_typeFlagBits = 0x0                m_level = 0                         m_flagBits = 0x8200
    m_objId (AllocUnitId.idObj) = 260   m_indexId (AllocUnitId.idInd) = 256 
    Metadata: AllocUnitId = 72057594054967296                                
    Metadata: PartitionId = 72057594048151552                                Metadata: IndexId = 0
    Metadata: ObjectId = 1698105090     m_prevPage = (0:0)                  m_nextPage = (0:0)
    pminlen = 72                        m_slotCnt = 100                     m_freeCnt = 396
    m_freeData = 7596                   m_reservedCnt = 0                   m_lsn = (55:8224:2)
    m_xactReserved = 0                  m_xdesId = (0:0)                    m_ghostRecCnt = 0
    m_tornBits = -2116084714            DB Frag ID = 1                      
    
    Allocation Status
    
    GAM (1:2) = ALLOCATED               SGAM (1:3) = NOT ALLOCATED          PFS (1:1) = 0x44 ALLOCATED 100_PCT_FULL
    DIFF (1:6) = CHANGED                ML (1:7) = NOT MIN_LOGGED           
    
    Slot 0 Offset 0x60 Length 75
    
    Record Type = PRIMARY_RECORD        Record Attributes =  NULL_BITMAP    Record Size = 75
    
    Memory Dump @0x0000000012A3A060
    
    0000000000000000:   10004800 61202020 20202020 20202020 20202020  ..H.a               
    0000000000000014:   20202020 20202020 20202020 20202020 20202020                      
    0000000000000028:   20202020 20202020 20202020 20202020 20202020                      
    000000000000003C:   20202020 20202020 20202020 010000                         ...
    
    Slot 0 Column 1 Offset 0x4 Length 68 Length (physical) 68
    
    filler = a                                                                   
    
    -- NOTE: The structure of each Slot is identical to that of Slot #0, so we can simply jump to slot 99:
    
    Slot 99 Offset 0x1d61 Length 75
    
    Record Type = PRIMARY_RECORD        Record Attributes =  NULL_BITMAP    Record Size = 75
    
    Memory Dump @0x0000000012A3BD61
    
    0000000000000000:   10004800 61202020 20202020 20202020 20202020  ..H.a               
    0000000000000014:   20202020 20202020 20202020 20202020 20202020                      
    0000000000000028:   20202020 20202020 20202020 20202020 20202020                      
    000000000000003C:   20202020 20202020 20202020 010000                         ...
    
    Slot 99 Column 1 Offset 0x4 Length 68 Length (physical) 68
    
    filler = a 
    

    所以我们可以看到最后一个槽在7521字节后开始,并且添加它的大小给我们7,596字节。如果我们添加slot数组的大小(每个指针是2个字节),我们得到7,796个字节。

    但是,我们需要达到8,192个字节来填充页面。缺少什么?

1 个答案:

答案 0 :(得分:3)

72K的保留空间包括64K范围(8页,每页8K)加上8K IAM页面开销。在这72K中,实际上只使用了IAM页面和单个数据页面。 sp_space_used报告index_size中的IAM页面,虽然技术上不是索引。您可以使用未记录的sys.dm_db_database_page_allocations TVF查看这些详细信息(仅在测试系统上使用):

SELECT extent_file_id, extent_page_id, page_type_desc
FROM sys.dm_db_database_page_allocations(DB_ID(), OBJECT_ID(N'dbo.MyTable'), 0, 1, 'DETAILED');

此数据库显然将MIXED_PAGE_ALLOCATION数据库选项设置为OFF,因此最初分配了一个完整的64K范围。如果该选项为ON,则将从混合范围而不是专用于该表的64K范围分配单个数据页。在这种情况下分配的空间将是16K - 一个8K单数据页加上IAM页。

尽管混合扩展区确实减少了小型表(64K以下)的空间需求,但混合扩展区具有更多开销,并且可能导致高并发工作负载中的分配争用,因此默认情况下它在SQL 2016以后关闭。在较旧的SQL版本中,默认情况下启用混合范围分配,并且可以使用跟踪标志1118在服务器级别关闭。

您可以在sys.databases中看到混合范围设置:

SELECT name, is_mixed_page_allocation_on
FROM sys.databases;

切换设置:

ALTER DATABASE Test
    SET MIXED_PAGE_ALLOCATION ON;

编辑1:

数据页面中的空间包括页面本身的开销以及页面内的记录。此开销加上用户数据所需的空间将决定页面上可容纳的行数和存储给定行数所需的数据页数。有关该开销的详细信息,请参阅Paul Randal的anatomy of a pageanatomy of a record文章。

编辑2:

根据您的后续评论:

  

7998字节,因此下一次分配有194个字节。   我错过了什么?

我几乎从不使用堆,但正如您在页面转储中看到的那样,此页面的关联PFS(页面可用空间)分配状态为100%已满。根据{{​​3}}书,PFS状态实际上是这些范围的3位掩码:

  • 000:空
  • 001:1-50%已满
  • 010:51-80%已满
  • 011:81-95%已满
  • 100:96-100%已满

所以看起来一旦堆页面丰满度超过96%的百分比阈值就被认为是100%已满并且分配了新页面。请注意,在具有聚簇索引的表上不会发生这种情况,因为新行的页面首先由CI键确定,并且只有在根本不适合该页面时才分配新页面。避免堆积的另一个原因。