为什么SQL Server使用索引扫描而不是Seek + RID查找?

时间:2014-05-30 10:22:38

标签: sql-server performance indexing

我有一张大约一张桌子。 135M行:

CREATE TABLE [LargeTable]
(
    [ID] UNIQUEIDENTIFIER NOT NULL,
    [ChildID] UNIQUEIDENTIFIER NOT NULL,
    [ChildType] INT NOT NULL
)

它有一个非聚集索引,没有包含列:

CREATE NONCLUSTERED INDEX [LargeTable_ChildID_IX]
  ON [LargeTable] 
(
    [ChildID] ASC
)

(它聚集在ID)。

我想加入一个包含几千行的临时表:

CREATE TABLE #temp
(
    ChildID         UNIQUEIDENTIFIER PRIMARY KEY,
    ChildType       INT
)

...add #temp data...

SELECT lt.ChildID, lt.ChildType
    FROM #temp t
    INNER  JOIN [LargeTable] lt
        ON lt.[ChildID] = t.[ChildID]

但是查询计划包括对大表的索引扫描:

Index Scan

如果我更改索引以包含额外的列:

CREATE NONCLUSTERED INDEX [LargeTable_ChildID_IX] ON [LargeTable] 
(
    [ChildID] ASC
)
INCLUDE [ChildType]

然后查询计划变为更明智的事情:

Index Seek

所以我的问题是:为什么SQL Server在第一种情况下仍然不能使用索引查找,但是使用RID查找从非聚集索引到表数据?当然,这会比这么大的桌子上的索引扫描更有效吗?

3 个答案:

答案 0 :(得分:1)

第一个查询计划实际上很有意义。请记住,SQL Server从不读取记录,而是读取页面。在您的表中,页面包含许多记录,因为这些记录非常小。

使用原始索引,如果将使用第二个查询计划,在查找索引中的所有RID并读取索引页面之后,需要读取聚簇索引中的页面以读取ChildType列。在最坏的情况下,这是它需要读取的每个记录的整个页面。由于每页有许多记录,这可能归结为读取聚集索引中的大部分页面。

SQL服务器根据统计数据猜测,只需扫描聚簇索引中的页面就需要更少的页面读取,因为它可以避免读取非聚集索引中的页面。

这里重要的是临时表中的数量与大表中页面的数量相比较。假设在大表中随机分发ChildID,一旦临时表中的行数接近或取代大表中的页数,SQL服务器就必须读取大表中的每个页面。< / p>

答案 1 :(得分:0)

由于列ChildType未包含在索引中,因此必须返回到聚簇索引(使用上面提到的行标识符查找)来获取ChildType的值。<登记/> 当您在非聚簇索引中INCLUDE此列时,它将被添加到可用于查询的索引的叶级别。

答案 2 :(得分:0)

通俗地称为'the index tipping point'。基本上,基于成本的优化器在什么时候认为执行扫描而不是搜索+查找更有效。通常是大小的20%左右,在您的情况下,这将基于来自#temp表统计的估计。 YMMV。

您已经有了答案:包含所需的列,使索引覆盖。