MSSQL Server 不在内部联接上使用非聚集复合键索引 (PK + FK)

MSSQL Server Not Using NonClustered Composite Key Index (PK + FK) on InnerJoin

具有以下结构:

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)

投标人的索引不相关。

请您多多包涵...此结构仅供您认识tables/data之间的关系,并非有意遵循最佳实践。实际的数据库确实更复杂(Table "Bid" 就像 5400 万行)。哦,是的,每个 Auction_Item 将只有一个 "Bid per Bidder" 出价最高和最低。

因此,当我执行以下查询时:

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 服务器不喜欢使用 "IX_Bid_IdBid_IdBidder",而将此执行计划用于 Bid:

如果我禁用 IX_Bid_IdBidder,并强制它使用 "IX_Bid_IdBid_IdBidder",一切都会变得一团糟:

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

更新: 正如@Arvo 所建议的,我更改了 "IX_Bid_IdBid_IdBidder" 的键列的顺序,使 Id_Bidder 第一,Id_Bid 第二。然后,它成为首选指标。那么,再一次,为什么 MSSQL 使用选择性较低的 "Index Key",而不是选择性最高的键? Id_Bid 在内连接中明确相关...

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

抱歉: 索引 IX_Bid_IdAuctionItem_IdBidder 实际上是 IX_Bid_IdBid_IdBidder,在索引唯一键中包含 Id_Bid!

SQL 服务器很少不使用覆盖的、正确排序的索引。只有病态的情况会浮现在脑海中,例如极低的页面填充度或大量不需要的额外列。

你的指数根本就没有覆盖。查看输出的列。您会发现一个您没有编入索引的。

该列是 Id_Auction_Item

有很多人比我更了解 SQL 服务器,但这听起来很像是两个可能的问题之一:

首先可能是 SQL 服务器正在使用过时的统计信息来确定什么是 "most efficient",并且由于统计信息错误,它选择了错误的索引。

第二种可能性要小得多,但值得一提。您没有在文本中提到存储过程,但如果这是在存储过程中,SQL 可能正在使用缓存的(而且是非常错误的)执行计划 - 查找 'parameter sniffing' 以获得更多解释主题。

好的,我认为经过大量研究(并更多地了解联接在幕后的真正工作方式)后我明白了。

到现在,我post只是作为一个理论,直到某些SQL师父说它是错误的并给我点亮,或者我真的确定我是对的。

重点是 MSSQL 正在选择对整个查询最快的查询,而不仅仅是对出价 table。所以分析器要选择从Auctiontable,或者Bidtable开始(因为我指定的条件。DateTime_Auction,和Id_Bidder)。 在我(轻率)的想法中,我认为最好的执行计划将从拍卖开始 table:

获取匹配指定日期的拍卖 >> 获取 Auctions_Items 与拍卖匹配的内部连接 ​​>> 获取与 Auction_Item 匹配的内部连接并且具有 Id_Bidder 匹配指定日期的出价编号

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

相反,MSSQL 希望从尽可能少的数据集开始。在这种情况下,只有指定投标人的投标,因为有很多投标人可能根本不参与的拍卖品。这样做,与 "my plan".

相比,每个嵌套循环的外部 table 都缩小了

获取指定投标人的投标 >> 与 Auction_Item 的内部连接 ​​>> 排除拍卖匹配日期。

如果你注意最右边的嵌套循环,我认为这是第一个嵌套循环,循环的外部 table 是预先selected 的出价列表使用适当索引 (IX_Bid_IdBidder) 的投标人,然后对聚集索引执行扫描,等等...

为了让它变得更好,我将 "IX_Bid_IdBid_IdBidder" 中的列包含到 "IX_Bid_IdBidder" 中,并且 MSSQL 不需要在 PK_Bid.

每次拍卖都有很多拍卖品,但每个拍卖品只有一个来自指定投标人的出价,所以第一个嵌套循环将select我们需要的有效拍卖品的最小值,这也将限制我们将考虑匹配日期的拍卖。因此,由于我们是从 Bids 开始的,所以没有 Id_Bids 的 "list" 来限制,然后 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