Performance 为什么这是索引扫描而不是索引查找?

Performance 为什么这是索引扫描而不是索引查找?,performance,tsql,sql-server-2008,indexing,sql-execution-plan,Performance,Tsql,Sql Server 2008,Indexing,Sql Execution Plan,问题是: SELECT top 100 a.LocationId, b.SearchQuery, b.SearchRank FROM dbo.Locations a INNER JOIN dbo.LocationCache b ON a.LocationId = b.LocationId WHERE a.CountryId = 2 AND a.Type = 7 位置索引: PK_位置: 位置ID IX_位置_国家ID_类型: Country

问题是:

SELECT      top 100 a.LocationId, b.SearchQuery, b.SearchRank
FROM        dbo.Locations a
INNER JOIN  dbo.LocationCache b ON a.LocationId = b.LocationId
WHERE       a.CountryId = 2
AND         a.Type = 7
位置索引:

PK_位置:

位置ID

IX_位置_国家ID_类型:

CountryId,类型

位置缓存索引:

PK_位置缓存:

位置ID

IX_LocationCache_LocationId_SearchQuery_SearchRank:

LocationId、SearchQuery、SearchRank

执行计划:

因此,它正在使用覆盖指数对位置进行索引搜索

但为什么它要对LocationCache覆盖索引进行索引扫描

该覆盖索引在索引中具有LocationId、SearchQuery、SearchRank(不作为“包含列”)

将鼠标悬停在索引扫描上:

此查询需要进入由SQL Server FTS目录提供的索引视图中,由自动完成插件使用,因此需要100%优化

目前,上述查询需要3秒钟。它应该小于0


有什么想法吗?

你有没有尝试更新你的统计数据

UPDATE STATISTICS dbo.LocationCache
下面是一些很好的参考资料,介绍了它的作用以及为什么查询优化器会选择扫描而不是搜索

总结

有几件事需要考虑 考虑一下。首先,当SQL 决定最好的(足够好) 计划使用,它查看查询, 然后再看看统计数据 它存储了关于桌子的信息 牵涉其中

然后,它决定是否需要更多 有效地向下搜索索引,或 扫描索引的整个叶级 (在这种情况下,涉及触摸 表中的每一页,因为它是 聚集索引)它通过 看看很多事情。 首先,它猜测有多少 需要扫描的行/页。这 被称为临界点,是一个 比你想象的要低。 看看这个伟大的金伯利Tripp博客

如果您在测试的限制范围内 引爆点,可能是因为你的 统计数据已过时,或者您的 索引是高度碎片化的

可以强制SQL查找 使用FORCESEEK查询进行索引 提示,但请将此与 一般来说,注意 保持一切正常,SQL 他非常擅长决定 最有效的计划是


请记住,当对其进行其他更改时,它将导致查询性能不佳,使用
内部循环联接应强制在
dbo.LocationCache
上使用覆盖索引

SELECT      top 100 a.LocationId, b.SearchQuery, b.SearchRank
FROM        dbo.Locations a
INNER LOOP JOIN dbo.LocationCache b ON a.LocationId = b.LocationId
WHERE       a.CountryId = 2
AND         a.Type = 7

它使用索引扫描主要是因为它也使用合并联接。Merge Join操作符需要两个输入流,它们都按照与连接条件兼容的顺序进行排序

它使用Merge-Join操作符来实现内部连接,因为它认为这将比更典型的嵌套循环连接操作符更快。它可能是对的(通常是),通过使用它选择的两个索引,它有输入流,这两个流都是根据您的连接条件(LocationID)预先排序的。当输入流像这样预先排序时,合并联接几乎总是比其他两个(循环联接和散列联接)更快

缺点是您已经注意到:它似乎在扫描整个索引,那么如果它读取了这么多可能从未使用过的记录,那么它怎么能更快呢?答案是,扫描(由于其顺序性)可以在任何地方读取的记录数是每秒查找记录数的10到100倍

现在搜索通常会赢,因为它们是有选择性的:它们只获取您要求的行,而扫描是非选择性的:它们必须返回范围内的每一行。但由于扫描具有更高的读取速率,只要丢弃行与匹配行的比率低于扫描行/秒与搜索行/秒的比率,扫描就可以频繁地击败搜索

有问题吗


好的,我被要求进一步解释最后一句话:

“丢弃行”是扫描读取的行(因为它必须读取索引中的所有内容),但会被合并联接运算符拒绝,因为它在另一侧没有匹配项,可能是因为WHERE子句条件已将其排除

“匹配行”是指它读取的、实际上与合并联接中的某个内容匹配的行。如果扫描被Seek替换,这些行将被Seek读取

通过查看查询计划中的统计信息,您可以了解其中有哪些内容。看到索引扫描左边那个巨大的胖箭头了吗?这表示优化器认为将通过扫描读取的行数。您发布的索引扫描的统计框显示实际返回的行约为540万行(5394402)。这等于:

TotalScanRows = (MatchingRows + DiscardedRows)
(用我的话说)。要获得匹配的行,请查看Merge Join操作符报告的“实际行”(您可能需要去掉前100行才能准确获得)。知道这一点后,您可以通过以下方式获得丢弃的行:

DiscardedRows = (TotalScanRows - MatchingRows)

现在你可以计算比率了。

我做了一个快速测试,得出了以下结论

CREATE TABLE #Locations
(LocationID INT NOT NULL ,
CountryID INT NOT NULL ,
[Type] INT NOT NULL 
CONSTRAINT PK_Locations
        PRIMARY KEY CLUSTERED ( LocationID ASC )
)

CREATE NONCLUSTERED INDEX [LocationsIndex01] ON #Locations
(
    CountryID ASC,
    [Type] ASC
)

CREATE TABLE #LocationCache
(LocationID INT NOT NULL ,
SearchQuery VARCHAR(50) NULL ,
SearchRank INT NOT NULL 
CONSTRAINT PK_LocationCache
        PRIMARY KEY CLUSTERED ( LocationID ASC )

)

CREATE NONCLUSTERED INDEX [LocationCacheIndex01] ON #LocationCache
(
    LocationID ASC,
    SearchQuery ASC,
    SearchRank ASC
)

INSERT INTO #Locations
SELECT 1,1,1 UNION
SELECT 2,1,4 UNION
SELECT 3,2,7 UNION
SELECT 4,2,7 UNION
SELECT 5,1,1 UNION
SELECT 6,1,4 UNION
SELECT 7,2,7 UNION
SELECT 8,2,7 --UNION

INSERT INTO #LocationCache
SELECT 4,'BlahA',10 UNION
SELECT 3,'BlahB',9 UNION
SELECT 2,'BlahC',8 UNION
SELECT 1,'BlahD',7 UNION
SELECT 8,'BlahE',6 UNION
SELECT 7,'BlahF',5 UNION
SELECT 6,'BlahG',4 UNION
SELECT 5,'BlahH',3 --UNION

SELECT * FROM #Locations
SELECT * FROM #LocationCache

SELECT      top 3 a.LocationId, b.SearchQuery, b.SearchRank
FROM        #Locations a
INNER JOIN  #LocationCache b ON a.LocationId = b.LocationId
WHERE       a.CountryId = 2
AND         a.[Type] = 7

DROP TABLE #Locations
DROP TABLE #LocationCache

对我来说,查询计划显示使用嵌套循环内部联接进行搜索。如果你运行这个,你会得到两个搜索吗?如果这样做,则在系统上进行测试,创建Locations和LocationCache表的副本,并使用所有索引调用它们,如Locations2和LocationCache2,然后将数据复制到其中。然后尝试查询新表?

简而言之:如果LocationCache上没有筛选器,则应返回整个表内容。你有一个完全覆盖索引。索引扫描(一次)是最便宜的操作,查询优化器会选择它

要优化: 你加入了整个表格,之后只得到前100名的结果。我不知道它们有多大,但尝试子查询[Locations]表