我正在运行以下简单脚本:
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
我的问题:
为什么要保留72 KB?
如果表甚至没有编入索引,为什么index_size为8 KB?
编辑:
我想添加一个跟进: 稍微更改脚本时:
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个字节来填充页面。缺少什么?
答案 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 page和anatomy of a record文章。
编辑2:
根据您的后续评论:
7998字节,因此下一次分配有194个字节。 我错过了什么?
我几乎从不使用堆,但正如您在页面转储中看到的那样,此页面的关联PFS(页面可用空间)分配状态为100%已满。根据{{3}}书,PFS状态实际上是这些范围的3位掩码:
所以看起来一旦堆页面丰满度超过96%的百分比阈值就被认为是100%已满并且分配了新页面。请注意,在具有聚簇索引的表上不会发生这种情况,因为新行的页面首先由CI键确定,并且只有在根本不适合该页面时才分配新页面。避免堆积的另一个原因。