源于csdn论坛的一个提问:
CREATE
TABLE
TUser ( FName
CHAR
(
8000
), FAge
INT
, FSex
bit
)
INSERT
INTO
TUser
SELECT
'
张三
'
,
18
,
1
UNION
ALL
SELECT
'
李四
'
,
20
,
1
UNION
ALL
SELECT
'
王五
'
,
32
,
1
UNION
ALL
SELECT
'
麻子
'
,
23
,
1
通过一个查询看下扫描的数据页:
SET
STATISTICS
IO
ON
SELECT
*
FROM
TUser
/*
(4 行受影响)
表 'TUser'。扫描计数 1,逻辑读取 4 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
*/
可以看到,该查询全部数据是扫了四个数据页,也就是说插入的四行数据,一行为一个page.
但是我们执行下面的sql,发现index_size为8k:
EXEC
sp_spaceused N
'
TUser
'
/*
name rows reserved data index_size unused
TUser 4 40 KB 32 KB 8 KB 0 KB
*/
这是为什么,为什么没有建索引,这里却有一个index_size 8k ?
下面来看看index_size是怎么来的?
首先想到是的
sp_helptext sp_spaceused
通过查看sp_spaceused的代码,我们找到对于我们这个查询有用的信息代码:
/*
** Now calculate the summary data.
* Note that LOB Data and Row-overflow Data are counted as Data Pages.
*/
SELECT
@reservedpages
=
SUM
(reserved_page_count),
@usedpages
=
SUM
(used_page_count),
@pages
=
SUM
(
CASE
WHEN
(index_id
<
2
)
THEN
(in_row_data_page_count
+
lob_used_page_count
+
row_overflow_used_page_count)
ELSE
lob_used_page_count
+
row_overflow_used_page_count
END
),
@rowCount
=
SUM
(
CASE
WHEN
(index_id
<
2
)
THEN
row_count
ELSE
0
END
)
FROM
sys.dm_db_partition_stats
WHERE
object_id
=
@id
;
/*
** Check if table has XML Indexes or Fulltext Indexes which use internal tables tied to this table
*/
IF
(
SELECT
count
(
*
)
FROM
sys.internal_tables
WHERE
parent_id
=
@id
AND
internal_type
IN
(
202
,
204
))
>
0
BEGIN
/*
** Now calculate the summary data. Row counts in these internal tables don't
** contribute towards row count of original table.
*/
SELECT
@reservedpages
=
@reservedpages
+
sum
(reserved_page_count),
@usedpages
=
@usedpages
+
sum
(used_page_count)
FROM
sys.dm_db_partition_stats p, sys.internal_tables it
WHERE
it.parent_id
=
@id
AND
it.internal_type
IN
(
202
,
204
)
AND
p.
object_id
=
it.
object_id
;
END
SELECT
name
=
OBJECT_NAME
(
@id
),
rows
=
convert
(
char
(
11
),
@rowCount
),
reserved
=
LTRIM
(
STR
(
@reservedpages
*
8
,
15
,
0
)
+
'
KB
'
),
data
=
LTRIM
(
STR
(
@pages
*
8
,
15
,
0
)
+
'
KB
'
),
index_size
=
LTRIM
(
STR
((
CASE
WHEN
@usedpages
>
@pages
THEN
(
@usedpages
-
@pages
)
ELSE
0
END
)
*
8
,
15
,
0
)
+
'
KB
'
),
unused
=
LTRIM
(
STR
((
CASE
WHEN
@reservedpages
>
@usedpages
THEN
(
@reservedpages
-
@usedpages
)
ELSE
0
END
)
*
8
,
15
,
0
)
+
'
KB
'
) 通过上面的代码,我们可以基于我们目前的这个情况(堆表,不含有xml和fulltext,所以上面的202/204那段不用管了)提练出index的page如下算法:
select
index_pagecount
=
sum
(used_page_count)
-
sum
(in_row_data_page_count
+
lob_used_page_count
+
row_overflow_used_page_count)
from
sys.dm_db_partition_stats
where
object_id
=
object_id
(
'
TUser
'
); 这个结果是1.再套上index_size的公式:
select
index_size
=
LTRIM
(
STR
((
1
)
*
8
,
15
,
0
)
+
'
KB
'
)
/*
8k
*/
这就是8k的算法,从下面联机文档上关于sys.dm_db_partition_stats
的解释,可以分析出这个8k是个IAM page.
used_page_count bigint
用于分区的总页数。计算方法为 in_row_used_page_count + lob_used_page_count + row_overflow_used_page_count
上面我们算index_page时公式为:
sum
(used_page_count)
-
sum
(in_row_data_page_count
+
lob_used_page_count
+
row_overflow_used_page_count)
也就是in_row_used_page_count
-in_row_data_page_count
对于这两列,联机文档解释是:
in_row_used_page_count
用于存储和管理分区中的行内数据的总页数。该计数包括非叶 B 树页、IAM 页以及 in_row_data_page_count 列包含的全部页。
in_row_data_page_count
分区中存储行内数据所用的页数。如果分区是堆的一部分,则该值为堆中的数据页数。如果分区是索引的一部分,则该值为叶级别中的页数。(未计入 B 树中非叶页的数目。)以上两种情况都未计入 IAM(索引分配映射)页。
针对我们目前该表的情况,仅是一个堆表,那么可知前者是包含了IAM页,而后者不含有IAM页,那么sp_spaceused中的index_size在这里就是一个IAM(索引分配映射)页。
其实这个也可以通过DBCC IND看到:Pagetype=10为IAM PAGE

如有错误,欢迎指正。
本文详细解析了SQL Server中堆表的存储原理,特别是如何通过sp_spaceused存储过程来计算IAM(索引分配映射)页的大小,并解释了为何在没有显式创建索引的情况下仍存在index_size。
143





