繁体   English   中英

SQL Server:未使用非聚集索引

[英]SQL Server: NonClustered index not used

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

表:

在此处输入图片说明

指数:

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'

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

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

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

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

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

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

如果此查询必须为您快速处理,请按照SQL Server的说明创建索引。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM