i-o*_*one 4 sql-server sql-server-2008-r2 sql-server-2014
假设我们有两个包含数据的表:
create table heap (value int);
create table clust (value int primary key);
insert into heap values (1);
insert into clust values (1);
Run Code Online (Sandbox Code Playgroud)
通过检查他们的存储统计
select obj.name, st.alloc_unit_type_desc, st.index_level, st.page_count
from (values ('heap'), ('clust')) obj(name)
join sys.indexes ix on ix.object_id = object_id(obj.name)
cross apply sys.dm_db_index_physical_stats(
db_id(), ix.object_id, ix.index_id, NULL, 'DETAILED') st;
select obj.name, au.total_pages, au.used_pages, au.data_pages
from (values ('heap'), ('clust')) obj(name)
join sys.indexes ix on ix.object_id = object_id(obj.name)
join sys.partitions p on p.object_id = ix.object_id and p.index_id = ix.index_id
join sys.allocation_units au on au.container_id = p.partition_id;
Run Code Online (Sandbox Code Playgroud)
可以看到两者占据相同的页数:
name alloc_unit_type_desc index_level page_count
----- -------------------- ----------- ----------
heap IN_ROW_DATA 0 1
clust IN_ROW_DATA 0 1
name total_pages used_pages data_pages
----- ------------ ----------- -----------
heap 2 2 1
clust 2 2 1
Run Code Online (Sandbox Code Playgroud)
在这种情况下为什么 statistics io
set nocount on;
set statistics io on;
declare @cnt int;
select @cnt = count(1) from heap;
select @cnt = count(1) from clust;
set statistics io off;
Run Code Online (Sandbox Code Playgroud)
显示扫描聚集索引与扫描堆相比需要额外的一次逻辑读取?
Table 'heap'. Scan count 1, logical reads 1...
Table 'clust'. Scan count 1, logical reads 2...
Run Code Online (Sandbox Code Playgroud)
这个额外的逻辑读是什么?
PS
我的问题不是关于“聚集索引与堆性能”或查询调优。我正在尝试更好地理解STATISTICS IO
为聚集索引扫描报告的内容。
该示例适用于 SqlServer 2014
Microsoft SQL Server 2014 (SP2) (KB3171021) - 12.0.5000.0 (X64)
我也在 SqlServer 2008 R2 上试过
Microsoft SQL Server 2008 R2 (SP3) - 10.50.6220.0 (X64)
结果是相同的(尽管不能sys.dm_db_index_physical_stats
以这种方式在 2008 R2 上使用)。
访问聚集索引或非聚集索引需要遍历该 b 树结构。出于某种原因,在扫描操作中似乎有一个额外的逻辑读取。在您的情况下,您有一个页面,即 2 个逻辑读取。如果您有足够的行来填充足够的数据页,而这些数据页又需要 b 树索引结构中的另一个级别,那么您将看到额外的逻辑读取。
根据定义,堆没有索引(b 树)结构。由于您有一个数据页,因此操作只需要进行一次逻辑读取。在这样一个简单的例子中,它似乎比聚集索引方法工作更少,但是一旦你获得更多的数据页,你就会开始看到差异,因为 b 树结构将允许直接转到适当的数据页,而堆仍然要检查所有的页。
例如,我有一个具有以下结构的测试表:
CREATE TABLE [dbo].[GuidPkAsUI](
[ID] [uniqueidentifier] NOT NULL CONSTRAINT [PK_GuidPkAsUI] PRIMARY KEY CLUSTERED,
[InsertTime] [datetime] NOT NULL
CONSTRAINT [DF_GuidPkAsUI_InsertTime] DEFAULT (getdate()),
);
Run Code Online (Sandbox Code Playgroud)
它通过以下方式有 767,968 行:
INSERT INTO [dbo].[GuidPkAsUI] ([ID])
SELECT TOP (767968) NEWID()
FROM master.sys.all_columns ac1
CROSS JOIN master.sys.objects so1;
Run Code Online (Sandbox Code Playgroud)
我将它复制到一个具有相同结构和数据的新表中,但使用以下内容缺少两个约束(即没有聚集索引):
SELECT *
INTO dbo.GuidPkAsUIheap
FROM dbo.GuidPkAsUI;
Run Code Online (Sandbox Code Playgroud)
以下查询:
SELECT * FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID(N'dbo.GuidPkAsUIheap'),
0, NULL, 'DETAILED');
SELECT * FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID(N'dbo.GuidPkAsUI'),
1, NULL, 'DETAILED');
Run Code Online (Sandbox Code Playgroud)
显示GuidPkAsUIheap
具有 1 个级别(具有 3135 个数据页)和GuidPkAsUI
3 个级别(具有 3135 个数据页,一个级别上的 20 个索引页——中级,以及另一个级别上的 1 个索引页——根,总共 3156 个页面)。
以下查询:
SET STATISTICS IO ON;
SELECT COUNT(1) FROM dbo.GuidPkAsUIheap;
SET STATISTICS IO OFF;
-- 3135
SET STATISTICS IO ON;
SELECT COUNT(1) FROM dbo.GuidPkAsUI;
SET STATISTICS IO OFF;
-- 3157
Run Code Online (Sandbox Code Playgroud)
显示GuidPkAsUIheap
需要 3135 次逻辑读取(数据页数),而GuidPkAsUI
需要 3157 次逻辑读取(数据和索引页数加一)。所以这里聚集索引的逻辑读取仍然高于堆。
然后我通过以下方式重建了表格:
ALTER TABLE [dbo].[GuidPkAsUIheap] REBUILD;
ALTER TABLE [dbo].[GuidPkAsUI] REBUILD WITH (FILLFACTOR = 100);
Run Code Online (Sandbox Code Playgroud)
SELECT * FROM sys.dm_db_index_physical_stats
再次运行上面的查询显示堆是相同的,但聚集索引现在只有 10 个中间索引页,而不是 20 个。
SELECT COUNT(1)
再次运行上面的查询显示堆的 3135 次逻辑读取和聚集索引的 3147 次逻辑读取(所有数据和索引页加 1)。
现在,让我们找到一个特定的行:
SET STATISTICS IO ON;
SELECT * FROM dbo.GuidPkAsUIheap WHERE [ID] = '93359759-193F-4CBF-B9F6-738475F8488E';
SET STATISTICS IO OFF;
-- 3135
SET STATISTICS IO ON;
SELECT * FROM dbo.GuidPkAsUI WHERE [ID] = '93359759-193F-4CBF-B9F6-738475F8488E';
SET STATISTICS IO OFF;
-- 3
Run Code Online (Sandbox Code Playgroud)
堆仍然需要 3135 次逻辑读取。但是聚集索引只需要 3 次逻辑读取:1 次用于根索引页,1 次用于下一级索引页,1 次用于叶级/数据页。
现在让我们在寻找一行时强制扫描:
SET STATISTICS IO ON;
SELECT * FROM dbo.GuidPkAsUIheap WHERE CONVERT(CHAR(36), [ID]) = '98331062-8BF3-4FAE-98B4-204D0DE06FE1';
SET STATISTICS IO OFF;
-- 3135
SET STATISTICS IO ON;
SELECT * FROM dbo.GuidPkAsUI WHERE CONVERT(CHAR(36), [ID]) = '98331062-8BF3-4FAE-98B4-204D0DE06FE1';
SET STATISTICS IO OFF;
-- 3147
Run Code Online (Sandbox Code Playgroud)
在这里,我们获得了与COUNT(1)
查询获得的相同的逻辑读取。