索引几乎相同的逻辑读取量差异

Hau*_*uri 5 performance sql-server query-performance

我试图弄清楚为什么在 SQL Server 中,两个相同的查询在两个几乎相同的索引下执行时花费的时间略有不同。提到的查询非常简单,具有以下形状>

set statistics io on
set statistics time on

SELECT t.field3
FROM Table t --WITH(INDEX(Index2))
WHERE t.field1 = 6 AND t.field2 = 'B' 
AND t.field3 >= CAST ('01-07-2012' As DateTime) AND t.field3 < CAST ('01-10-2012' As DateTime)
ORDER BY t.field3
Run Code Online (Sandbox Code Playgroud)

如果我在 Table t(包含 52M 行)上运行此查询,则检索到的统计信息为:

  • 逻辑读数:95
  • CPU时间:15毫秒
  • 经过时间:6 ms

如果我检查查询的实际执行计划,它会在 Index1 上显示 100% 的 Index Seek,它是非聚集的,并按此顺序包含字段 field1、field2、field3。但是,如果我取消注释强制使用 Index2 的行,它包含相同顺序的相同字段,但它是聚集的(主键),统计信息是:

  • 逻辑读取:1456
  • CPU时间:15毫秒
  • 已用时间:12 毫秒

虽然花费的总时间并没有特别糟糕,但我想了解这里发生了什么。谁能给我一个解释?除了时差很小之外,还有性能差异吗?

非常感谢 !!

Tho*_*ger 6

这一切都取决于非聚集索引中定义的定义和键(和非键)列。聚集索引是实际的表数据。因此,它包含数据页中的所有数据,而非聚集索引仅包含在索引创建 DDL 中定义的列数据。

让我们建立一个测试场景:

use testdb;
go

if exists (select 1 from sys.tables where name = 'TestTable')
begin
    drop table TestTable;
end
create table dbo.TestTable
(
    id int identity(1, 1) not null
        constraint PK_TestTable_Id primary key clustered,
    some_int int not null,
    some_string char(128) not null,
    some_bigint bigint not null
);
go

create unique index IX_TestTable_SomeInt
on dbo.TestTable(some_int);
go

declare @i int;
set @i = 0;

while @i < 1000
begin
    insert into dbo.TestTable(some_int, some_string, some_bigint)
    values(@i, 'hello', @i * 1000);

    set @i = @i + 1;
end
Run Code Online (Sandbox Code Playgroud)

所以我们有一个加载了 1000 行的表,以及一个聚集索引 ( PK_TestTable_Id) 和一个非聚集索引 ( IX_TestTable_SomeInt)。正如您在测试中所见,只是为了彻底:

set statistics io on;
set statistics time on;

select some_int
from dbo.TestTable -- with(index(PK_TestTable_Id));

set statistics io off;
set statistics time off;
-- nonclustered index scan (IX_TestTable_SomeInt)
-- logical reads: 4
Run Code Online (Sandbox Code Playgroud)

这里我们对索引进行了非聚集索引扫描IX_TestTable_SomeInt。对于此操作,我们有 4 个逻辑读取。现在让我们强制使用聚集索引。

set statistics io on;
set statistics time on;

select some_int
from dbo.TestTable with(index(PK_TestTable_Id));

set statistics io off;
set statistics time off;
-- clustered index scan (PK_TestTable_Id)
-- logical reads: 22
Run Code Online (Sandbox Code Playgroud)

这里使用聚集索引扫描,我们有 22 个逻辑读取。为什么?这是为什么。重要的是 SQL Server 必须读取多少页才能获取整个结果集。获取每页的平均行数:

select
    object_name(i.object_id) as object_name,
    i.name as index_name,
    i.type_desc,
    ips.page_count,
    ips.record_count,
    ips.record_count / ips.page_count as avg_rows_per_page
from sys.dm_db_index_physical_stats(db_id(), object_id('dbo.TestTable'), null, null, 'detailed') ips
inner join sys.indexes i
on ips.object_id = i.object_id
and ips.index_id = i.index_id
where ips.index_level = 0;
Run Code Online (Sandbox Code Playgroud)

看看我上面查询的结果集:

在此处输入图片说明

正如我们在这里看到的,聚集索引的叶页上平均每页有 50 行,非聚集索引的叶页上平均每页有 500 行。因此,为了满足查询需要从聚集索引中读取更多的页面。