EF Core 中的查询超时,但在 SSMS 中运行速度很快

Posted

技术标签:

【中文标题】EF Core 中的查询超时,但在 SSMS 中运行速度很快【英文标题】:Query Timeout in EF Core but runs fast in SSMS 【发布时间】:2021-11-04 02:09:27 【问题描述】:

我有一个用例,我将查询传递给函数,然后进行一些计算。查询是根据我通过的过滤器形成的。下面是示例代码

var totalCount = await query.CountAsync();

var limitExceeded = limit.HasValue && totalCount > limit.Value;
var pagedResults = new List<R>();
// Don't execute the query if the limit has been exceeded
if (!limitExceeded)

    //Do some work here

我面临的问题是有一个名为“文件名”的过滤器,当我通过该过滤器时,生成的基础查询如下所示

DECLARE @__fileName_1 nvarchar(1024) = N'%cmder.zip%';

SELECT [d].[Id], [d].[CreatedByUserId], [d].[DateCreated], [d].[DateModified], [d].[DatePurged], [d].[Deleted], [d].[DocumentKey], [d].[DocumentStatusId], [d].[DocumentTypeId], [d].[FileDesc], [d].[FileExt], [d].[FileLength], [d].[FileLengthTypeId], [d].[FileName], [d].[FileSize], [d].[FullPath], [d].[Hidden], [d].[ModifiedByUserId], [d].[PurgedByUserId], [d].[RepositoryId], [d].[SHA1], [d].[TransactionId], [d].[UploadedDate], [c].[Id], [c].[CaseId], [c].[DateModified], [c].[Deleted], [c].[DocumentId], [c].[ModifiedByUserId], [c].[PublishToId], [c].[TransactionId], [c0].[Id], [c0].[CaseCoordinatorId], [c0].[CaseName], [c0].[CaseNo], [c0].[CaseTypeId], [c0].[CaseVenueTypeId], [c0].[County], [c0].[Court], [c0].[DateModified], [c0].[DateSettled], [c0].[Deleted], [c0].[Disabled], [c0].[FullCaseName], [c0].[IsComplex], [c0].[IsDepository], [c0].[ModifiedByUserId], [c0].[NameKey], [c0].[Remarks], [c0].[SalesRepId], [c0].[TransactionId], [c0].[TrialDate], [c0].[USStateId], [l].[Name] AS [PublishTo], N'Case' AS [Level]
FROM [Documents].[Document] AS [d]
INNER JOIN [Orders].[CaseDocument] AS [c] ON [d].[Id] = [c].[DocumentId]
INNER JOIN [Orders].[Case] AS [c0] ON [c].[CaseId] = [c0].[Id]
INNER JOIN [Admin].[LookupValue] AS [l] ON [c].[PublishToId] = [l].[Id]
WHERE [d].[FileName] LIKE @__fileName_1
ORDER BY [d].[UploadedDate] DESC

现在这个查询在 SQL Server Management Studio 上运行得非常快,但是当我调试我的 C# 代码时,一旦执行达到await query.CountAsync(),它就会开始在 UI 上加载,然后在一段时间后它会超时。有人可以帮我调试吗?当我发送其他过滤器(例如日期)时,它工作正常,但是当我发送名称时,它开始花费时间,但仅在 C# 端,因为我检查了生成的查询运行得非常快。

【问题讨论】:

LIKE @__fileName_1 when @__fileName_1 = N'%cmder.zip%' 总是很慢,因为它需要整个表扫描。您希望避免在字符串中搜索字符串,因为无法使用索引。 当某些东西在 SSMS 中速度快而在 ADO.NET 中速度较慢时,通常归结为 任一 不同的查询选项(SSMS 与 ADO.NET 的默认值不同)或参数嗅探——不管怎样,问题是当你在 SSMS 中运行它时,你会得到一个不同的查询计划;见sommarskog.se/query-plan-mysteries.html;添加OPTION(OPTIMIZE FOR UNKNOWN) 至少可以帮助它保持一致(尽管也应该谨慎使用) @Andrew 有很多行,你在上面看到的查询是由 ef core 生成的,所以我无法控制,你知道 linq 中的一种方法我可以做到吗?跨度> 如果它是由 LINQ 生成的,那么它真的很难做诸如添加提示之类的事情;我的观点:LINQ 非常棒当它完美运行时;当它开始给你带来悲伤的那一刻:停止这样做 - 获取生成的查询,并使用 EF 原始 API 或 Dapper 执行该查询进行任何你需要的调整 好吧,全文搜索有搜索内容的限制,可能不适合这种情况。将FileName 拆分为两个字段怎么样:PathFileName 【参考方案1】:

由于您的 Document 表中有很多行,因此您正在对整个表进行非 sargable 扫描!这并不理想。理想情况下,您希望使其可搜索,因此请删除前导或尾随 % 符号。

例如如果您删除前导 % 那么它将能够使用 FirstName 列上的索引(只要您创建一个)。然后它可以寻找匹配项并只读取该数据而不是整个表。

你可以在你的 linq 代码中尝试这样的事情

YourDocuments.Where(x => EF.Functions.Like(x.FileName, $"yourSearchString%")) 

如果您想要更多控制权,请考虑使用存储过程,但如果这样做了,那么一切都很好:)

如果您刚刚在 FileName 上创建了一个索引并保留了 %...% 语法,它可能会使用上面其他人提到的索引,它仍然是对该索引的完整扫描。索引将小于聚集索引表,因此 SQL Server 会使用它,因为它会执行较少的逻辑页面读取。

【讨论】:

所以你是在告诉我,即使我的表中的文件名上有一个非聚集索引,如果搜索类似于“Like %xxx%”,那么它不会使用该索引?它只会在我删除前导 % 时使用? 它可能会使用索引,但它仍然是一个完整的扫描,(底部的更新答案)因为任何被 %...% make 包围的东西都是不可分割的,然后你必须扫描所有行。如果您可以提前过滤结果,例如搜索日期范围或用户 ID,那么您可以限制全表扫描,但它仍然必须扫描基于先前过滤匹配的每一行。引用 brent ozar 的话,这都是关于“选择性”的,所以如果你可以选择最小的数据集并寻找它,查询会更快。 这是有用的还是我们没有提供您想要的? 嘿 Andrew 感谢您的回答,它确实有效并且性能大幅提升,但这种方法存在一个小问题。问题是 %abc% 会搜索所有包含 abc 的字符串,但 abc% 只会搜索以 abc 开头而不是包含 abc 的字符串。

以上是关于EF Core 中的查询超时,但在 SSMS 中运行速度很快的主要内容,如果未能解决你的问题,请参考以下文章

如何编写转换为 T-SQL 的 EF Core 查询包含

过程在 ADO.NET 中超时,但在 SSMS 中没有

我的查询在 SSMS 中很快,但在我的应用程序中超过 1 小时

插入查询在 SSMS 中有效,但在 SQLCMD 中无效

使用 linq 计算 EF Core 中的平均评分

存储过程中的动态 SQL 不返回结果集;在 SSMS 中运行时,我得到了结果