分析表无索引为什么sp_spaceused 中的index_size不为0

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

源于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深度巡检脚本 SET NOCOUNT ON; -- 1. 系统信息 SELECT SERVERPROPERTY('MachineName') AS [MachineName], SERVERPROPERTY('ServerName') AS [ServerName], SERVERPROPERTY('InstanceName') AS [InstanceName], SERVERPROPERTY('Edition') AS [Edition], SERVERPROPERTY('ProductVersion') AS [ProductVersion], SERVERPROPERTY('ProductLevel') AS [ProductLevel], @@VERSION AS [VersionInfo] -- 2. 配置参数(部分重要参数) SELECT name, value_in_use, description FROM sys.configurations WHERE name IN ( 'max server memory (MB)', 'min server memory (MB)', 'cost threshold for parallelism', 'max degree of parallelism', 'optimize for ad hoc workloads' ) -- 3. 数据库基本信息 SELECT name, state_desc, recovery_model_desc, log_reuse_wait_desc, user_access_desc, compatibility_level FROM sys.databases -- 4. 磁盘空间使用情况(按数据库文件) SELECT DB_NAME(database_id) AS [DatabaseName], name AS [FileName], type_desc, physical_name, size * 8 / 1024 AS [SizeMB], FILEPROPERTY(name, 'SpaceUsed') * 8 / 1024 AS [UsedMB], (size - FILEPROPERTY(name, 'SpaceUsed')) * 8 / 1024 AS [FreeMB] FROM sys.master_files -- 5. 性能计数器(部分关键计数器,通过动态管理视图) -- 5.1 缓冲池使用情况 SELECT * FROM sys.dm_os_buffer_pool_extension_configuration; -- 5.2 页面预期寿命(Page Life Expectancy) SELECT [object_name], cntr_value AS [PageLifeExpectancy] FROM sys.dm_os_performance_counters WHERE [object_name] LIKE '%Buffer Manager%' AND counter_name = 'Page life expectancy' -- 6. 索引状态(碎片和缺失索引) -- 6.1 索引碎片 SELECT DB_NAME(ips.database_id) AS [Database], OBJECT_NAME(ips.object_id) AS [Table], si.name AS [Index], ips.avg_fragmentation_in_percent FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') ips JOIN sys.indexes si ON ips.object_id = si.object_id AND ips.index_id = si.index_id WHERE ips.avg_fragmentation_in_percent > 30 -- 碎片大于30%的索引 -- 6.2 缺失索引 SELECT mid.statement AS [Database.Table], migs.avg_total_user_cost * migs.avg_user_impact * (migs.user_seeks + migs.user_scans) AS [Improvement_Measure], 'CREATE INDEX [IX_' + OBJECT_NAME(mid.object_id) + '_' + REPLACE(REPLACE(mid.equality_columns, ', ', '_'), '[', '') + CASE WHEN mid.inequality_columns IS NULL THEN '' ELSE '_' + REPLACE(REPLACE(mid.inequality_columns, ', ', '_'), '[', '') END + '] ON ' + mid.statement + ' (' + ISNULL(mid.equality_columns, '') + CASE WHEN mid.equality_columns IS NOT NULL AND mid.inequality_columns IS NOT NULL THEN ',' ELSE '' END + ISNULL(mid.inequality_columns, '') + ')' + ISNULL(' INCLUDE (' + mid.included_columns + ')', '') AS [CreateIndexScript] FROM sys.dm_db_missing_index_group_stats migs JOIN sys.dm_db_missing_index_groups mig ON migs.group_handle = mig.index_group_handle JOIN sys.dm_db_missing_index_details mid ON mig.index_handle = mid.index_handle WHERE migs.avg_total_user_cost * migs.avg_user_impact * (migs.user_seeks + migs.user_scans) > 10 ORDER BY [Improvement_Measure] DESC -- 7. 等待事件(按等待类型统计) SELECT wait_type, waiting_tasks_count, wait_time_ms, max_wait_time_ms, signal_wait_time_ms FROM sys.dm_os_wait_stats WHERE wait_type NOT IN ('CLR_SEMAPHORE', 'LAZYWRITER_SLEEP', 'RESOURCE_QUEUE', 'SLEEP_TASK', 'SLEEP_SYSTEMTASK', 'WAITFOR', 'LOGMGR_QUEUE') ORDER BY wait_time_ms DESC -- 8. 慢查询(需要启用查询存储或开启跟踪,这里提供查询存储的示例,需SQL Server 2016+) -- 假设数据库已启用查询存储 SELECT qsrs.avg_duration, qsrs.avg_logical_io_reads, qsrs.avg_rowcount, qst.query_sql_text FROM sys.query_store_runtime_stats qsrs JOIN sys.query_store_plan qsp ON qsrs.plan_id = qsp.plan_id JOIN sys.query_store_query qsq ON qsp.query_id = qsq.query_id JOIN sys.query_store_query_text qst ON qsq.query_text_id = qst.query_text_id WHERE qsrs.last_execution_time > DATEADD(day, -1, GETDATE()) -- 最近一天 ORDER BY qsrs.avg_duration DESC -- 如果没有启用查询存储,可以考虑使用sys.dm_exec_query_stats,但只能统计自上次重启以来的数据 -- 9. 死锁监控(需要开启死锁跟踪或使用扩展事件,这里查询系统健康会话中的死锁信息) IF OBJECT_ID('tempdb..#deadlock_report') IS NOT NULL DROP TABLE #deadlock_report CREATE TABLE #deadlock_report (EventXML XML) INSERT INTO #deadlock_report SELECT CAST(event_data AS XML) FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL) WHERE event_data LIKE '%<event name="xml_deadlock_report"%' SELECT EventXML.value('(event/@timestamp)[1]', 'datetime') AS [Time], EventXML.query('//event/data/value/deadlock') AS DeadlockGraph FROM #deadlock_report -- 10. 作业状态(最近24小时失败作业) SELECT sj.name AS [JobName], sjh.run_status, sjh.run_date, sjh.run_time FROM msdb.dbo.sysjobs sj JOIN msdb.dbo.sysjobhistory sjh ON sj.job_id = sjh.job_id WHERE sjh.run_status = 0 -- 失败 AND CONVERT(DATETIME, CONVERT(CHAR(8), sjh.run_date, 112) + ' ' + STUFF(STUFF(REPLACE(STR(sjh.run_time,6,0),' ','0'),3,0,':'),6,0,':')) > DATEADD(day, -1, GETDATE()) -- 11. 备份信息(最近备份情况) SELECT database_name, type, backup_start_date, backup_finish_date, backup_size / 1024 / 1024 AS [BackupSizeMB] FROM msdb.dbo.backupset WHERE backup_start_date > DATEADD(day, -7, GETDATE()) -- 最近一周 ORDER BY backup_start_date DESC -- 12. 安全审计(检查登录和用户) -- 12.1 检查登录名 SELECT name, type_desc, is_disabled FROM sys.server_principals WHERE type IN ('S', 'U', 'G') AND name NOT LIKE '##%' -- 12.2 检查数据库用户 USE [master] -- 切换到具体数据库则需修改 GO EXEC sp_MSforeachdb ' USE [?] SELECT DB_NAME() AS [Database], name AS [UserName], type_desc, authentication_type_desc FROM sys.database_principals WHERE type IN (''S'', ''U'', ''G'') AND name NOT IN (''dbo'', ''guest'', ''sys'', ''INFORMATION_SCHEMA'') ' 给我这个脚本字段加上中文别名
最新发布
12-20
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值