MSSQL服务器在内部联接上不使用非聚簇复合键索引(PK + FK)

时间:2015-01-20 06:55:00

标签: sql sql-server sql-server-2008

具有以下结构:

Table Auction (Id_Auction (Pk), DateTime_Auction)
Table Auction_Item (Id_Auction_Item (Pk), Id_Auction (Fk), Id_Winning_Bid (Fk), Item_Description)
Table Bid (Id_Bid (Pk), Id_Auction_Item (Fk), Id_Bidder (Fk), Lowest_Value, Highest_Value)
Table Bidder (Id_Bidder (Pk), Name)

拍卖指数无关紧要。

Auction_Item的索引:

Clustered Index PK_Auction_Item (Id_Auction_Item)
NonClustered Index IX_Auction_Item_IdWinningBid (Id_Winning_Bid)

出价指数:

Clustered Index PK_Bid (Id_Bid)
NonClustered Index IX_Bid_IdBidder (Id_Bidder)
NonClustered Index IX_Bid_IdBid_IdBidder (Id_Bid, Id_Bidder) Unique Included (Id_Auction_Item, Lowest_Value, Highest_Value)

投标人的索引无关紧要。

我会请你稍微承认一下......这种结构只是让你认识到表/数据之间的关系,并不打算遵循最佳实践。实际的数据库确实更复杂(表“Bid”就像5400万行)。哦,是的,每个Auction_Item只有一个“每个投标人的出价”,其出价最高和最低。

所以,当我执行以下查询时:

Select 
     Auc.Id_Auction,
     Itm.Id_Auction_Item,
     Itm.Item_Description,
     B.Id_Bid,
     B.Lowest_Value,
     B.Highest_Value

From
     Auction Auc
     Inner Join Auction_Item Itm on Itm.Id_Auction = Auc.Id_Auction
     Inner Join Bid B on B.Id_Bid = Itm.Id_Winning_Bid
                         And B.Id_Bidder = 27

Where Auc.DateTime_Auction > '2014-01-01';

为什么Sql Server不使用“IX_Bid_IdBid_IdBidder”,并将此执行计划用于出价

Preferred execution plan

如果我禁用IX_Bid_IdBidder,并强制它使用“IX_Bid_IdBid_IdBidder”,一切都搞乱了:

enter image description here

我无法理解为什么MSSQL更喜欢使用2个索引,而不是只有一个完全覆盖查询的索引。我唯一的猜测是使用ClusteredIndex更快,但我不相信它比仅使用其他NonClustered Index的Unique Composite Key更快。
为什么呢?

更新 正如@Arvo提出的,我更改了“IX_Bid_IdBid_IdBidder”的键列顺序,使Id_Bidder成为第一,Id_Bid成为秒。然后,它成为首选索引。那么,为什么MSSQL使用选择性较低的“索引键”而不是最具选择性的键? Id_Bid在内部联接中明确相关...

旧更新: 我更新了查询,使其更具选择性。 另外,我更新了索引“IX_Bid_IdBid_IdBidder”,以包含Id_Auction_Item

道歉: 索引IX_Bid_IdAuctionItem_IdBidder实际上是IX_Bid_IdBid_IdBidder,它包含在INDEX UNIQUE KEY中的Id_Bid!

3 个答案:

答案 0 :(得分:2)

SQL Server很少使用覆盖,正确排序的索引。只有病态案例才会出现,例如极低的页面填充度或非常不需要的额外列。

您的索引根本就没有涵盖。查看输出的列。您将发现一个尚未编入索引的文件。

该列为Id_Auction_Item

答案 1 :(得分:1)

好吧,我认为经过大量的研究(并且更多地了解了联接如何在幕后工作),我想出来了。

到现在为止,我只是将其作为一种理论发布,直到一些SQL大师说它错了并向我展示了光明,或者我确定我是对的。

重点是MSSQL正在选择整个查询的最快速度,而不仅仅是Bid表。所以分析器必须选择从Auction表或Bid表开始(因为我指定的条件.DateTime_Auction和Id_Bidder)。 在我(轻浮)的思想中,我认为最好的执行计划将从拍卖表开始:

获取与指定日期匹配的拍卖>>获取Auctions_Items匹配内部联接与拍卖>>获取与Auction_Item匹配内部联接的投标,并使Id_Bidder与指定的ID匹配

这会在每个" level" /嵌套循环中选择很多行,并且最后只使用指定的索引来排除90%的数据。

相反,MSSQL希望尽可能从最小的数据集开始。在这种情况下,只有指定投标人的投标,因为投标人可能根本没有参与的拍卖物品很多。这样做,每个嵌套循环的外表都缩小了与#34;我的计划"相比。

获取指定投标人的出价>>与Auction_Item>>的内部联接不包括与日期匹配的拍卖。

如果你注意最右边的嵌套循环,我认为是第一个嵌套循环,循环的外表是使用适当索引(IX_Bid_IdBidder)预先选择的投标人投标列表,而不是执行扫描聚集索引等...

为了使它更好,我列出了" IX_Bid_IdBid_IdBidder"中的列。进入" IX_Bid_IdBidder",并且MSSQL不需要在PK_Bid上执行密钥查找。

每次拍卖都有很多拍卖物品,但每个拍卖物品只有一个来自指定投标人的投标,因此第一个嵌套循环将选择我们需要的最少有效拍卖物品,这也将限制拍卖我们将考虑匹配日期。因此,由于我们从Bids开始,因此没有"列表"对Id_Bids进行限制,然后MSSQL不能使用索引" IX_Bid_IdBid_IdBidder"即使它涵盖了查询的所有领域。现在思考,似乎有点明显。

无论如何,感谢所有帮助过我的人!


我的研究:
http://sqlmag.com/database-performance-tuning/advanced-join-techniques(有点过时......)
https://technet.microsoft.com/en-us/library/ms191426%28v=sql.105%29.aspx
https://technet.microsoft.com/en-us/library/ms191318%28v=sql.105%29.aspx
http://blogs.msdn.com/b/craigfr/archive/2006/07/26/679319.aspx
http://blogs.msdn.com/b/craigfr/archive/2009/03/18/optimized-nested-loops-joins.aspx

答案 2 :(得分:0)

那里有很多人比我更了解SQL Server,但这听起来很像两个可能的问题之一:

首先,可能是SQL Server使用过时的统计信息来确定最有效的"以及由于统计信息错误,它选择了错误的索引。

第二种情况不太可能,但值得一提。您没有在文本中提到存储过程,但如果这是在存储过程中,则SQL可能正在使用缓存(并且非常错误)的执行计划 - 查找参数嗅探'有关此主题的更多解释。

相关问题