【问题标题】:SQL Server: NonClustered index not usedSQL Server:未使用非聚集索引
【发布时间】:2014-11-01 16:50:53
【问题描述】:

我已经阅读了很多关于索引和它们之间的差异的内容。现在我正在我的项目中进行查询优化。我创建了非聚集索引,应该在查询执行时使用,但事实并非如此。详情如下:

表:

索引:

CREATE NONCLUSTERED INDEX [_IXProcedure_Deleted_Date] ON [por].[DailyAsset]
(
    [Deleted] ASC,
    [Date] DESC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

实体框架生成的查询:

exec sp_executesql N'SELECT 
[Project1].[C1] AS [C1], 
[Project1].[AssetId] AS [AssetId], 
[Project1].[Active] AS [Active], 
[Project1].[Date] AS [Date]
FROM ( SELECT 
    [Extent1].[AssetId] AS [AssetId], 
    [Extent1].[Active] AS [Active], 
    [Extent1].[Date] AS [Date], 
    1 AS [C1]
    FROM [por].[DailyAsset] AS [Extent1]
    WHERE (0 = [Extent1].[Deleted]) AND ([Extent1].[Date] < @p__linq__0)
)  AS [Project1]
ORDER BY [Project1].[Date] DESC',N'@p__linq__0 datetime2(7)',@p__linq__0='2014-05-01 00:00:00'

执行计划:

缺少索引详细信息:

The Query Processor estimates that implementing the following index could improve the query cost by 23.8027%.


CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [por].[DailyAsset] ([Deleted],[Date])
INCLUDE ([AssetId],[Active])

我知道,如果将 AssetId 和 Active 列包含在索引中,将使用索引。

现在,为什么不包含列就无法工作?

这是另一个查询的简化示例,其中所有列都作为结果被提取。 (强制)使用索引查找的唯一解决方案是将所有列包含在索引中,这具有相同的估计子树成本(很明显)。

这里另一个烦人的问题是排序无知。日期列在索引中并设置为 DESCENDING。它被完全忽略了,当然,排序操作在执行计划中占据了昂贵的位置。

更新 1:

正如@Jayachandran 所指出的,IndexSeek + KeyLookUp 应该在上面的查询中使用,但是覆盖索引是有据可查的,它假设应该包括 AssetId 和 Active 列。我同意。

我正在创建 UPDATE 1 以在下面的查询中演示覆盖索引的有用性。同一张表,更大的结果集。据我所知,不应在索引中使用单个列,并且为 Date 和 Deleted 列创建索引。

exec sp_executesql N'SELECT 
[Project1].[DailyAssetId] AS [DailyAssetId], 
[Project1].[AssetId] AS [AssetId], 
[Project1].[CreatedByUserId] AS [CreatedByUserId], 
[Project1].[UpdatedByUserId] AS [UpdatedByUserId], 
[Project1].[TimeCreated] AS [TimeCreated], 
[Project1].[TimeUpdated] AS [TimeUpdated], 
[Project1].[Deleted] AS [Deleted], 
[Project1].[TimeDeleted] AS [TimeDeleted], 
[Project1].[DeletedByUserId] AS [DeletedByUserId], 
[Project1].[Active] AS [Active], 
[Project1].[Date] AS [Date], 
[Project1].[Quantity] AS [Quantity], 
[Project1].[TotalBookValue] AS [TotalBookValue], 
[Project1].[CostPrice] AS [CostPrice], 
[Project1].[CostValue] AS [CostValue], 
[Project1].[FairPrice] AS [FairPrice], 
[Project1].[FairValue] AS [FairValue], 
[Project1].[UnsettledQuantity] AS [UnsettledQuantity], 
[Project1].[UnsettledValue] AS [UnsettledValue], 
[Project1].[SettlementDate] AS [SettlementDate], 
[Project1].[EffectiveDate] AS [EffectiveDate], 
[Project1].[PortfolioId] AS [PortfolioId]
FROM ( SELECT 
    [Extent1].[DailyAssetId] AS [DailyAssetId], 
    [Extent1].[AssetId] AS [AssetId], 
    [Extent1].[CreatedByUserId] AS [CreatedByUserId], 
    [Extent1].[UpdatedByUserId] AS [UpdatedByUserId], 
    [Extent1].[TimeCreated] AS [TimeCreated], 
    [Extent1].[TimeUpdated] AS [TimeUpdated], 
    [Extent1].[Deleted] AS [Deleted], 
    [Extent1].[TimeDeleted] AS [TimeDeleted], 
    [Extent1].[DeletedByUserId] AS [DeletedByUserId], 
    [Extent1].[Active] AS [Active], 
    [Extent1].[Date] AS [Date], 
    [Extent1].[Quantity] AS [Quantity], 
    [Extent1].[TotalBookValue] AS [TotalBookValue], 
    [Extent1].[CostPrice] AS [CostPrice], 
    [Extent1].[CostValue] AS [CostValue], 
    [Extent1].[FairPrice] AS [FairPrice], 
    [Extent1].[FairValue] AS [FairValue], 
    [Extent1].[UnsettledQuantity] AS [UnsettledQuantity], 
    [Extent1].[UnsettledValue] AS [UnsettledValue], 
    [Extent1].[SettlementDate] AS [SettlementDate], 
    [Extent1].[EffectiveDate] AS [EffectiveDate], 
    [Extent1].[PortfolioId] AS [PortfolioId]
    FROM [por].[DailyAsset] AS [Extent1]
    WHERE (0 = [Extent1].[Deleted]) AND ([Extent1].[Date] < @p__linq__0)
)  AS [Project1]
ORDER BY [Project1].[Date] DESC',N'@p__linq__0 datetime2(7)',@p__linq__0='2014-05-01 00:00:00'

【问题讨论】:

  • 我猜是因为选择包括 AssetId 和 Active 列。所以使用索引不会得到所有需要的数据。因此它强制扫描。将这些列添加到索引将使选择仅通过索引搜索获取所有数据。一旦你强制索引搜索,排序问题就会自行解决。目前没有使用您的索引,因此索引中的排序日期没有相关性。
  • 是的,但正如我在 and 中所写,假设我们正在选择所有列(这些查询确实存在于我的项目中,在同一张表上)而不仅仅是这三个。那么,我应该在索引中包含所有列吗?这既不实用也不有用..我认为。
  • 它应该是一个没有覆盖索引的带有键查找的索引搜索。不确定为什么您的执行计划假设索引扫描比这便宜,但出于某种原因它确实如此;可能是不正确的统计数据或其他东西。但是是的,覆盖索引是一种有据可查的查询调优方法。
  • 我会在索引中交换 Date 和 Deleted。
  • @Jayachandran:我已经用 UPDATE 1 编辑了我的问题。我对覆盖索引语句是否正确?

标签: sql sql-server non-clustered-index


【解决方案1】:

在这种情况下,扫描和查找(使用键查找)的区别在于返回的行数。体积太大,因此优化器选择了一个更便宜的计划 - 只需扫描整个表。这将比使用 NC 索引更快。

想象一下,如果您强制它使用 NC 索引,它必须对表中 40% 的行进行键查找。这就像执行多次的 foreach 循环。所以 SQL 选择只扫描表,因为它比循环更快。

关于您关于如何考虑可能包含在其他查询中的其他列的问题,实际上有几个选择。您可以创建一个包含最常用列的覆盖索引,也可以更改主键以使其朝向最常用的访问路径。即按日期,删除和唯一性标识列。

另一方面,对主键使用 guid 会导致聚集索引和所有其他索引出现各种问题(因为 PK 的键将包含在所有其他索引中)。 guid 的随机排序导致行以随机顺序插入到页面中。因为索引是有序的,所以必须不断地拆分页面以考虑新行。创建一个自然递增的索引会好得多,这也可能有助于解决上述问题,具体取决于编写的查询类型。

【讨论】:

  • 结合@ZoffDino 的评论,我的一切都清楚了。
  • 为了讨论起见,关于 GUID 作为您提到的 PK。 SQL 服务器在 2005 版本中引入了 NEWSEQUENTIALID()。它应该可以解决您在评论中提到的问题,或者..?
  • 是的,如果该功能始终在同一台计算机上运行,​​这将大大减少问题。 Windows 重新启动会导致它重置,但这应该很少见,因此无关紧要。我还会考虑 PK 的大小,因为它将是所有其他索引的一部分。您可以制作的占用空间越小,行大小越小,每页的行数越多,页数就越少。这可能会对性能产生重大影响。
【解决方案2】:

特定查询的理想索引是:(1) WHERE 子句中的所有字段都在索引中,(2) SELECT 子句中的所有字段都包含在索引中。如果 (1) 不满足,SQL Server 将权衡访问多个索引的成本,并选择它认为最快的一个;如果 (2) 不满足,则意味着昂贵的 Key Lookup 操作。如果索引具有非常高的选择性(重复值很少),SQL Server 可能认为这是值得的。

在您的情况下,显然不满足条件 (2)。 SQL Server 认为 Key Lookup 操作与聚集索引扫描相比过于昂贵,因此选择了后者。您可以强制 SQL Server 使用特定索引,但我不知道如何使用 Entity Framework 执行此操作。

如果此查询对您来说必须很快,请按照 SQL Server 的说明创建索引。

【讨论】:

  • 我已经按照 SQL Server 的说法编辑了索引,并且探查器显示相同的执行持续时间(在当前数据集上),但“读取”要少得多(我的索引读取 360 次,SQL Server 建议索引读取 66 次。你和马克的回答对我帮助很大。
猜你喜欢
  • 2012-10-01
  • 2013-08-20
  • 2017-04-01
  • 2019-09-12
  • 2018-05-08
  • 2014-04-27
  • 2013-03-22
  • 2011-10-07
相关资源
最近更新 更多