You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EF Core 8.0.17中Skip/Take分页查询超时的性能问题求助

EF Core 8.0.17中Skip/Take分页查询超时的性能问题求助

各位大佬,我碰到了一个EF Core 8.0.17的诡异性能问题,想请教下大家:

我做了一个文档元数据的查询功能,用EF.Functions.Contains调用全文索引搜索文档内容。现在的情况是:

  • 当我给查询加上Skip/Take做分页时,请求直接超时了
  • 但如果去掉分页逻辑,直接执行整个查询,虽然慢一点但最终能返回结果
  • 更奇怪的是,我把EF生成的SQL拿出来在SSMS里直接执行,居然不到1秒就跑完了

我的查询构建代码(DocumentRepository.cs)

var documents = _context.Document
    .Include(d => d.DocumentType).AsSplitQuery().AsNoTracking()
    .Include(d => d.DocumentStatus).AsSplitQuery().AsNoTracking()
    .Include(d => d.OriginationType).AsSplitQuery().AsNoTracking()
    .Where(d => d.DeleteDate == null)
    .AsNoTracking()
    .AsQueryable();

if (!string.IsNullOrEmpty(documentSearchParams.DocumentText))
{
    documents = documents.Where(d => EF.Functions.Contains(d.DocumentText, searchCondition.ToString()));
}
if (!string.IsNullOrEmpty(documentSearchParams.DocumentTitle))
{
    documents = documents.Where(d => d.Title.Contains(documentSearchParams.DocumentTitle));
}
// 其他搜索参数的过滤逻辑...

// 设置排序
documents = documents.OrderBy(d => d.CreateDate);

// 只选择返回结果需要的字段
var d = documents.Select(doc => new Document()
{
    DocumentId = doc.DocumentId,
    PublicationDate = doc.PublicationDate,
    Title = doc.Title,
    DocumentNumber = doc.DocumentNumber,
    Author = doc.Author,
    DocumentType = doc.DocumentType,
    DocumentTypeId = doc.DocumentTypeId,
    DocumentStatus = doc.DocumentStatus,
    DocumentStatusId = doc.DocumentStatusId,
    Summary = doc.Summary,
    Keywords = doc.Keywords,
    FileName = doc.FileName,
    DocumentPersons = doc.DocumentPersons,
    CreatedPerson = doc.CreatedPerson,
    CreatedPersonId = doc.CreatedPersonId,
    CreateDate = doc.CreateDate,
    UpdateDate = doc.UpdateDate,
    UpdatedPersonId = doc.UpdatedPersonId,
    UpdatedPerson = doc.UpdatedPerson
});

return await PagedList<Document>.CreateAsync(d, documentSearchParams.PageNumber, documentSearchParams.PageSize);

分页逻辑代码(PagedList.cs)

public static async Task<PagedList<T>> CreateAsync(IQueryable<T> source, int pageNumber, int pageSize)
{
    var count = await source.CountAsync();
    var items = new List<T>();

    if (count > pageSize)
    {
        var numberToSkip = pageSize * pageNumber;
        source = source
            .Skip(numberToSkip)
            .Take(pageSize);
        items = await source.ToListAsync();
    }
    else
    {
        items = await source.ToListAsync();
    }

    return new PagedList<T>(items, count, pageNumber, pageSize);
}

EF生成的SQL片段

DECLARE @__8__locals1_documentSearchParams_DocumentText_0_contains nvarchar(4000) = N'%test%';
DECLARE @__Trim_1_contains nvarchar(4000) = N'%test%';
DECLARE @__ToString_3 nvarchar(4000) = N'"*test*"';
DECLARE @__p_4 int = 0;
DECLARE @__p_5 int = 3;

SELECT [t].[DocumentId], [t].[PublicationDate], [t].[Title], [DocumentNumber], [t].[Author], [d0].[DocumentTypeId], [d0].[Code], [d0].[CreateDate], [d0].[CreatedPersonId], [d0].[DeleteDate], [d0].[DeletedPersonId], [d0].[IsActive], [d0].[Name], [d0].[SortOrder], [d0].[UpdateDate], [d0].[UpdatedPersonId], [t].[DocumentTypeId], [d1].[DocumentStatusId], [d1].[Description], [d1].[SortOrder], [d1].[StatusCode], [t].[DocumentStatusId], [t].[Summary], [t].[Keywords], [t].[FileName], [t].[MissionAreaId], [p].[PersonId], [p0].[PersonId], [d3].[DocumentId], [d3].[PersonId], [p].[DomainName], [p].[Email], [p].[FirstName], [p].[LastName], [p].[ObjectName], [t].[CreatedPersonId], [t].[CreateDate], [t].[UpdateDate], [t].[UpdatedPersonId], [p0].[DomainName], [p0].[Email], [p0].[FirstName], [p0].[LastName], [p0].[ObjectName]
FROM (
    SELECT [d].[DocumentId], [d].[Author], [d].[AuthorIdList], [d].[Budget], [d].[ClassificationTypeId], [d].[ClassifiedTitle], [d].[CreateDate], [d].[CreatedPersonId], [d].[DocumentNumber], [d].[DocumentStatusId], [d].[DocumentTypeId], [d].[FileName], [d].[IsProtected], [d].[Keywords], [d].[MissionAreaId], [d].[OriginatingGroup], [d].[PublicationDate], [d].[ReviewId], [d].[SortingTitle], [d].[Summary], [d].[Title], [d].[UpdateDate], [d].[UpdatedPersonId]
    FROM [Document] AS [d]
    WHERE [d].[Title] LIKE @__8__locals1_documentSearchParams_DocumentText_0_contains ESCAPE N'\' OR [d].[DocumentNumber] LIKE @__Trim_1_contains ESCAPE N'\' OR CONTAINS([d].[DocumentText], @__ToString_3) OR LOWER([d].[Summary]) LIKE @__8__locals1_documentSearchParams_DocumentText_0_contains ESCAPE N'\' OR [d].[Author] LIKE @__8__locals1_documentSearchParams_DocumentText_0_contains ESCAPE N'\' OR [d].[Title] LIKE @__8__locals1_documentSearchParams_DocumentText_0_contains ESCAPE N'\'
    ORDER BY [d].[PublicationDate] DESC
    OFFSET @__p_4 ROWS FETCH NEXT @__p_5 ROWS ONLY
) AS [t]
LEFT JOIN [DocumentType] AS [d0] ON [t].[DocumentTypeId] = [d0].[DocumentTypeId]
LEFT JOIN [DocumentStatus] AS [d1] ON [t].[DocumentStatusId] = [d1].[DocumentStatusId]
INNER JO

(注:SQL内容被截断,核心分页逻辑已包含在子查询中)

现在我特别困惑:为什么EF执行带Skip/Take的查询会超时,但直接跑生成的SQL却这么快?有没有什么EF Core的配置或者查询写法的坑我没注意到?比如AsSplitQuery、AsNoTracking的使用是否有问题?或者是EF处理分页时产生了额外的性能开销?

希望有遇到过类似问题的大佬能给点思路,谢谢啦!

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 10:37:58