了解 sp_spaceused 值:保留值和 index_size
Understanding sp_spaceused values: reserved and index_size
我是运行下面的简单脚本:
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?
如果 table 甚至没有索引,为什么 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:
这是 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 字节。如果我们加上槽数组的大小(其中每个指针为 2 个字节),我们将得到 7,796 个字节。
但是,我们需要达到 8,192 字节才能填满页面。少了什么东西?
保留的 72K space 包括一个 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,则单个数据页将从混合盘区分配,而不是专用于 table 的 64K 盘区。在那种情况下分配的 space 将是 16K - 一个 8K 的单个数据页加上 IAM 页。
尽管混合区确实减少了 space 对小 table 的要求(低于 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:
数据页中的 Space 包括页面本身的开销以及页面内的记录。此开销加上用户数据所需的 space 将决定一个页面可以容纳多少行以及存储给定行数所需的数据页数。有关该开销的详细信息,请参阅 Paul Randal 的 anatomy of a page and anatomy of a record 文章。
编辑 2:
来自您的后续评论:
7998 bytes, so there are more 194 bytes to go for the next allocation.
What am I missing?
我几乎从不使用堆,但正如您在页面转储中所见,此页面的相关 PFS(页面空闲 space)分配状态为 100% 满。根据 Kalen Delaney's Microsoft SQL Server 2012 Internals 书,PFS 状态实际上是这些范围的 3 位掩码:
- 000: 空
- 001:1-50% 已满
- 010:51-80% 已满
- 011:81-95% 已满
- 100:96-100% 满
所以看起来一旦堆页面充满度超过 96% 的阈值,它就被认为是 100% 满,并且分配了一个新页面。请注意,这不会发生在具有聚集索引的 table 上,因为新行的页面首先由 CI 键确定,并且仅当新页面无法放入该页面时才分配新页面全部。避免堆的另一个原因。
我是运行下面的简单脚本:
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?
如果 table 甚至没有索引,为什么 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:
这是 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 字节。如果我们加上槽数组的大小(其中每个指针为 2 个字节),我们将得到 7,796 个字节。
但是,我们需要达到 8,192 字节才能填满页面。少了什么东西?
保留的 72K space 包括一个 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,则单个数据页将从混合盘区分配,而不是专用于 table 的 64K 盘区。在那种情况下分配的 space 将是 16K - 一个 8K 的单个数据页加上 IAM 页。
尽管混合区确实减少了 space 对小 table 的要求(低于 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:
数据页中的Space 包括页面本身的开销以及页面内的记录。此开销加上用户数据所需的 space 将决定一个页面可以容纳多少行以及存储给定行数所需的数据页数。有关该开销的详细信息,请参阅 Paul Randal 的 anatomy of a page and anatomy of a record 文章。
编辑 2:
来自您的后续评论:
7998 bytes, so there are more 194 bytes to go for the next allocation. What am I missing?
我几乎从不使用堆,但正如您在页面转储中所见,此页面的相关 PFS(页面空闲 space)分配状态为 100% 满。根据 Kalen Delaney's Microsoft SQL Server 2012 Internals 书,PFS 状态实际上是这些范围的 3 位掩码:
- 000: 空
- 001:1-50% 已满
- 010:51-80% 已满
- 011:81-95% 已满
- 100:96-100% 满
所以看起来一旦堆页面充满度超过 96% 的阈值,它就被认为是 100% 满,并且分配了一个新页面。请注意,这不会发生在具有聚集索引的 table 上,因为新行的页面首先由 CI 键确定,并且仅当新页面无法放入该页面时才分配新页面全部。避免堆的另一个原因。