我有一个Entity类,它生成一个相当简单的表:(混淆)
public class TableName
{
public int TableNameId { get; set; }
public int OtherTableId { get; set; }
public int UserId { get; set; }
public bool IsActive { get; set; }
// removed other columns for brevity
public virtual OtherTable OtherTable { get; set; }
public virtual User User { get; set; }
}
实体自动在TableNameId上创建主键,在OtherTableId和UserId上创建索引的外键。
对于UserId和OtherTableId的每个组合,该表只有一条记录,其中IsActive为真,但记录被停用并经常更换。
记录的声明类似于:
var record =
context.TableName
.FirstOrDefault(x => x.UserId == userId
&& x.OtherTableId == otherTableId
&& x.IsActive);
我们发现,没有其他索引,拉动记录时的性能很差,因为它必须扫描UserId和OtherTableId索引才能找到IsActive = 1的记录。
为了解决这个问题,我们添加了这个索引:
create nonclustered index ix_TableName_UserId_OtherTableId_IsActive
on TableName (UserId, OtherTableId, IsActive)
但是,SQL不使用它。相反,使用此索引,它会对表的主键进行扫描!
由于Entity正在拉动整行,我理解上面的索引没有覆盖。但是,在所有其他情况下,我们已经能够添加与此类似的索引,并且它会导致索引搜索与键查找相结合并提供令人满意的性能。在这种情况下,除非我们在select语句中使用索引提示,否则它不会使用我们正在创建的索引。我们想避免这种情况,因为它需要自定义SQL,这会破坏Code First Entity的目的。
经过多次试验和错误后,SQL将自己使用的唯一索引是包含每个列的索引,因此完全覆盖。但是,我宁愿避免这种情况,因为它意味着基本上存储表两次,因为IsActive = 0,所以99%的行都是无用的。
--- 编辑 ---
对于上面的代码,我发现Entity生成以下SQL。我不知道它为什么使用子选择,但SQL优化器似乎将其过滤掉(在同一计划中运行完整的SQL或内部选择结果)。
SELECT TOP (1)
[Project1].[TableNameId] AS [TableNameId],
[Project1].[OtherTableId] AS [OtherTableId],
[Project1].[UserId] AS [UserId],
[Project1].[IsActive] AS [IsActive]
FROM ( SELECT
[Extent1].[TableNameId] AS [TableNameId],
[Extent1].[OtherTableId] AS [OtherTableId],
[Extent1].[UserId] AS [UserId],
[Extent1].[IsActive] AS [IsActive]
FROM [dbo].[TableName] AS [Extent1]
WHERE ([Extent1].[UserId] = <userId>)
AND ([Extent1].[OtherTableId] = <otherTableId>)
AND ([Extent1].[IsActive] = 1)
) AS [Project1]