以下是查询:
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,Type
LocationCache索引:
PK_LocationCache:
LocationId
IX_LocationCache_LocationId_SearchQuery_SearchRank:
LocationId,SearchQuery,SearchRank
执行计划:
所以它在Locations上进行索引搜索,使用覆盖索引,很酷。
但是为什么它在覆盖索引的LocationCache上进行索引扫描?
该覆盖索引在索引中具有LocationId,SearchQuery,SearchRank(而不是“包含的列”)。
将鼠标悬停在索引扫描上:
此查询需要进入由自动完成插件使用的SQL Server FTS目录提供的索引视图,因此需要100%优化。
此时上述查询需要3秒钟。它应该是< 0
有什么想法吗?
答案 0 :(得分:32)
它主要使用索引扫描,因为它也使用合并连接。 Merge Join运算符需要两个输入流,这两个输入流都按照与Join条件兼容的顺序排序。
它正在使用Merge Join运算符来实现您的INNER JOIN,因为它相信它会比更典型的Nested Loop Join运算符更快。并且它可能是正确的(通常是),通过使用它选择的两个索引,它具有根据您的连接条件(LocationID)预先排序的输入流。当输入流像这样预先排序时,Merge Joins几乎总是比其他两个(Loop和Hash Joins)更快。
缺点是您已经注意到:它似乎是在扫描整个索引,所以如果它读取那么多可能永远不会被使用的记录,那怎么会更快?答案是,扫描(由于它们的顺序性质)可以读取任何数量的记录/秒的10到100倍。
现在寻求通常赢,因为它们是有选择性的:它们只获得你要求的行,而扫描是非选择性的:它们必须返回范围内的每一行。但是因为扫描具有多更高的读取速率,只要丢弃行与匹配行的比率更低,它们就可以经常击败Seeks,而不是扫描行/秒的比率VS.寻找行数/秒。
有问题吗?
好的,我被要求更多地解释最后一句话:
“Discarded Row”是Scan读取的行(因为它必须读取索引中的所有内容),但是Merge Join运算符会拒绝它,因为它在另一侧没有匹配,可能是因为WHERE子句条件已经排除了它。
“匹配行”是它读取的那些实际上与Merge Join中的某些内容匹配的行。如果扫描由Seek替换,则这些行与Seek读取的行相同。
您可以通过查看查询计划中的统计信息来确定其中的内容。看到Index Scan左侧那个巨大的胖箭头?这表示优化器认为它将使用Scan读取的行数。您发布的索引扫描的统计信息框显示返回的实际行数约为5.4M(5,394,402)。这等于:
TotalScanRows = (MatchingRows + DiscardedRows)
(就我而言,无论如何)。要获得匹配行,请查看Merge Join运算符报告的“实际行”(您可能必须取下TOP 100才能准确地获得此值)。一旦你知道这一点,就可以通过以下方式获得Discarded行:
DiscardedRows = (TotalScanRows - MatchingRows)
现在你可以计算比率了。
答案 1 :(得分: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
答案 2 :(得分:4)
您是否尝试更新统计信息?
UPDATE STATISTICS dbo.LocationCache
以下是一些很好的参考资料,介绍了查询优化程序将通过搜索选择扫描的原因和原因。
摘要的
有几件事需要考虑 考虑在这里。首先,当SQL 决定最好的(足够好) 计划使用,它查看查询, 然后还看看统计数据 它存储有关表格 参与。
然后决定是否更多 有效地寻找指数,或 扫描索引的整个叶级 (在这种情况下,它涉及触摸 表中的每一页,因为它是 聚集索引)它通过这样做 看着很多东西。 首先,它猜测了多少 它需要扫描的行/页面。这个 被称为引爆点,是一个 比你想象的更低的百分比。 看到这个伟大的金伯利特里普博客 http://www.sqlskills.com/BLOGS/KIMBERLY/category/The-Tipping-Point.aspx
如果你在...的范围内 引爆点,可能是因为你的 统计数据已过期,或者您的 索引严重分散。
可以强制SQL查找 使用FORCESEEK查询索引 提示,但请使用此 一般情况下,谨慎提供给您 保持所有维护,SQL 很擅长决定什么 最有效的计划将是!!
答案 3 :(得分:2)
简而言之:您没有对LocationCache进行过滤,应返回整个表内容。你有一个完全覆盖索引。索引SCAN(一次)是最便宜的操作,查询优化器会选择它。
优化:
您正在加入整个表格,之后只获得前100名结果。我不知道它们有多大,但是尝试子查询[Locations]表CountryId, Type
然后只使用[LocationCache]加入结果。如果你有超过1000行,将会更快。
另外,如果可能,请尝试在连接之前添加一些限制性更强的过滤器。
索引扫描: 由于扫描触及表中的每一行,无论其是否合格,因此成本与表中的总行数成比例。因此,如果表很小或者大多数行符合谓词的条件,则扫描是一种有效的策略。
索引寻求: 由于搜索仅触及限定行和包含这些限定行的页面,因此成本与合格行和页面的数量成比例,而不是与表中的总行数成比例。
如果表上有索引,并且查询触及大量数据,这意味着查询正在检索超过50%或90%的数据,然后优化器将只扫描所有数据用于检索数据行的页面。
答案 4 :(得分: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,并将所有索引复制到它们中。然后尝试查询新表?