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]表