2011-06-30 239 views
14

这里的查询:为什么这是索引扫描而不是索引搜索?

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_Locations:

LocationId

IX_Locations_CountryId_Type:

CountryId,类型

LocationCache指标:

PK_LocationCache:

LocationId

IX_LocationCache_LocationId_SearchQuery_SearchRank:

LocationId,SEARCHQUERY,SearchRank

执行计划:

enter image description here

所以它做一个索引查找上位置,使用覆盖索引,冬暖夏凉。

但是为什么它正在对LocationCache覆盖索引进行索引扫描

覆盖索引在索引中包含LocationId,SearchQuery,SearchRank(而不是“包含的列”)。

悬停在索引扫描:

enter image description here

此查询需要通过一个SQL Server FTS目录,由自动完成插件消费服务索引视图去,所以它必须是100%的优化。

上述查询需要3秒。它应该是< 0.

任何想法?

+1

可能不相关,但我很好奇,为什么不通过同时使用'顶100' –

+1

出于兴趣(但并非是任何形式的修复)有一个顺序会改变'INNER JOIN'到'INNER LOOP JOIN'加快速度或减慢速度? –

+0

你的主键是否有机会聚集在一起? – JStead

回答

8

虽然记住,这将导致在可以作为表现很差,并且当额外变化向它提出,利用INNER LOOP JOIN应强制要在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 
+1

原来我无法使用它,因为它需要进入索引视图(因为FTS目录需要)。但带有聚集PK的索引视图不能有提示。 :( – RPM1984

+1

另一只小猫刚刚去世:〜( –

+0

@ Pure.Krome--我希望这是一只意图统治世界的邪恶的小猫 –

4

您是否尝试过更新统计信息?

UPDATE STATISTICS dbo.LocationCache 

这里有几个很好的参考资料,说明了做什么以及为什么查询优化器会选择扫描搜索。

http://social.msdn.microsoft.com/Forums/en-CA/sqldatabaseengine/thread/82f49db8-0c77-4bce-b26c-1ad0a4af693b

摘要

有几件事情要考虑到 考虑这里。首先,当SQL 决定使用最佳(足够好)的 计划时,它查看查询 ,然后查看它存储的关于涉及的表 的统计 。

然后决定是否更 有效地寻求了指数,或 扫描索引 的全叶级(在这种情况下,它涉及感人 表中的每一页,因为它是 一个聚簇索引)它通过 看了很多东西。 首先,它猜测需要扫描多少个行/页。这个 被称为转折点,并且比您想象的要低百分之 。 看这大金佰利特里普博客 http://www.sqlskills.com/BLOGS/KIMBERLY/category/The-Tipping-Point.aspx

如果您对 引爆点的范围内,这可能是因为你的 统计信息已过时的,或者你 指数是大量碎片。

它可以强制SQL使用FORCESEEK查询 提示寻求 指数,但请使用此与 小心,因为一般情况下,为您提供 把一切都WEEL维护,SQL 是决定相当不错的什么 最有效的计划将是!

+0

是的,试过了,就像上面的评论一样。 – RPM1984

+0

@ RPM1984我没有注意到还有13条评论 – cordsen

+0

+1链接到金佰利Tripp的优秀博客文章 –

29

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

它使用合并连接运算符来实现您的INNER JOIN,因为它相信这将比更典型的嵌套循环连接运算符更快。它可能是正确的(通常是),通过使用它选择的两个索引,它具有输入流,它们都根据您的连接条件(LocationID)进行了预先排序。当输入流像这样预先排序时,Merge Joins几乎总是比另外两个(Loop和Hash Joins)更快。

缺点是你已经注意到:它似乎是在扫描整个索引,所以如果它正在阅读那么多可能永远不会被使用的记录,那么它会如何更快?答案是Scans(因为它们的连续性)可以读取任意数量的记录/秒的10到100倍的任意位置。

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

有问题?


OK,我一直要求解释的最后一句话更多:

“已放弃行”是指在扫描读取(因为它具有读取索引的所有内容),但将被合并连接运算符拒绝,因为它在另一侧没有匹配,可能是因为WHERE子句条件已经排除了它。

“匹配行”是它读取的实际上与合并连接中某些事物相匹配的那些行。如果Scan被Seek取代,这些行将被Seek读取。

您可以通过查看查询计划中的统计数据来弄清楚有什么。看到索引扫描左边那个巨大的胖箭头?这表示优化程序认为它将通过扫描读取多少行。您发布的索引扫描的统计框显示返回的实际行数约为5.4M(5,394,402)。这等于:

TotalScanRows = (MatchingRows + DiscardedRows) 

(以我的意思,无论如何)。要获得匹配行,请查看合并连接运算符报告的“实际行数”(您可能需要取消前100位以准确获取)。一旦你知道这一点,你可以得到被丢弃的行:

DiscardedRows = (TotalScanRows - MatchingRows) 

现在你可以计算出比率。

+0

mind == blown(不好的方式)。问题?是的,报价:“丢弃的行数与匹配行数的比率低于扫描行数/秒VS.寻求行数/秒”< - 请你详细说明这个..和HOW我们可以用这个来检查这个统计信息,等等。 –

+1

+1这是一个非常有用的答案。查看了我的数字,它正在做一个400000行的_Index Scan_,但只匹配_Hash Join_中的201,因此我猜测_Loop Join_会很远根据比率更高效。 –

+0

您的帖子似乎非常有意义。你能指导我,如何计算比率**扫描行数/秒** vs **寻求行数/秒** –

0

我做了一个快速测试,并具有以下

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,并将你的数据复制到它们中。然后尝试你的查询命中新表?

+3

可能是因为你的表中没有5.4M行。 – RBarryYoung

+0

大声笑很好,我提到尝试新表的原因是我前一段时间在两个有三千四百多万行的桌子上有类似的问题,我没有做任何事情使它按照它应该的方式工作。但在一个狡猾的测试服务器上,它工作得很好,速度很快。在创建新表格并复制数据之后,它非常高兴。我仍然不知道是什么导致了这个问题,统计数据可能我只是不知道。 –

+0

什么是沮丧..当我有同样的问题..并使用UPDATE STATISTICS ...我不知道它有助于:( –

2

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

要优化: 您正在加入整个表格,并且以后只能获得前100个结果。我不知道他们有多大,但尝试子查询[位置]表CountryId, Type,然后通过[位置缓存]加入结果。如果您的行数超过1000行,速度会更快。 另外,如果可能,请尝试在连接之前添加更多限制性过滤器。

索引扫描: 由于扫描触及表中的每一行而不管它是否合格,因此成本与表中的总行数成比例。因此,如果表很小或者大多数行符合谓词,则扫描是一种有效的策略。

索引搜寻: 由于seek只触及符合条件的行和包含这些符合条件的行的页,因此代价与限定行和页的数量成正比,而不是与表中行的总数成比例。

如果在表上有一个索引,并且查询正在触及大量的数据,这意味着查询正在检索超过50%或90%的数据,然后优化器只会扫描所有数据页面来检索数据行。

source

相关问题