大发快3_快3最新网站_大发快3最新网站 - 大发快3,快3最新网站,大发快3最新网站提供图书、电影、音乐唱片的推荐、评论和价格比较,以及城市独特的文化生活。

好大一个坑: EF Core 异步读取大字符串字段比同步慢100多倍

  • 时间:
  • 浏览:0

这半年遇到有另一个 奇怪的问题图片图片,通过 EF/EF Core 查询数据库数率奇慢,先是在传统的 ASP.NET 项目中遇到(用的是EF6.0),随后 将该项目迁移至 ASP.NET Core 也是同样的问题图片图片(用的是EF Core 2.2.2)。

问题图片图片触发的条件是所查询的字段中存储了很大的字符串(有1000多万个字符),查询耗时竟然要40s左右(对,是40秒),CPU消耗也很高,2核CPU消耗1000%-1000%左右,而加进去 Dapper 则没这些 问题图片图片。

通过 EF Core 的 Debug 日志跟踪发现,耗时地处在执行完 DbCommand 与 dispose DbDataReader 之间:

2019-02-23 15:46:27.026 [Information] Executed DbCommand ("4"ms) [Parameters=[""], CommandType='Text', CommandTimeout='1000']"
2019-02-23 15:47:06.859 [Debug] A data reader was disposed.

通过日志跟踪信息看,很容易会怀疑耗时随后 地处在 ADO.NET DataReader 读取数据时,但这些 怀疑与 Dapper 查询正常矛盾,随后 CPU 消耗高也说明耗时也有出现在 IO 层面。

随后 在 stackoverflow 上找到了线索 Poor performance when loading entity with large string property using Entity Framework

I had the same issue yesterday. What I did find out is that async operations with Entity Framework is broken or at least very slow. Try using the same operations synchronously

当时看一遍了这些 线索,不得劲不相信,异步竟然会引起这些 问题图片图片,也有默认都使用异步吗?本来 我抱着试试看的心理将代码中的 ToListAsync() 改为 ToList() ,结果却让人大吃一惊,多次测试,查询耗时在 1000-10000 ms 之间,快了 1000 多倍。

更新

触发这些 问题图片图片有 3 个条件:

1)读取的字符串很大

2)使用 DbCommand.ExecuteReaderAsync 异步最好的方式读取

3)调用 ExecuteReaderAsync 时这么 给 behavior 参数传值 CommandBehavior.SequentialAccess

在 Dapper 中这么 出现问题图片图片是随后 Dapper 中设置了 CommandBehavior.SequentialAccess ,详见 Dapper 的源代码 SqlMapper.Async.cs#L945

using (var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false))
{
    //...
}

EF Core 中会出现这些 问题图片图片是随后 EF Core 调用的是 ExecuteReaderAsync(CancellationToken cancellationToken) ,这么 设置 CommandBehavior ,详见 EF Core 的源代码 RelationalCommand.cs#L292

result = new RelationalDataReader(
    connection,
    dbCommand,
    await dbCommand.ExecuteReaderAsync(cancellationToken),
    commandId,
    Logger);

关于 CommandBehavior.SequentialAccess 详见微软官方文档

Provides a way for the DataReader to handle rows that contain columns with large binary values. Rather than loading the entire row, SequentialAccess enables the DataReader to load data as a stream. You can then use the GetBytes or GetChars method to specify a byte location to start the read operation, and a limited buffer size for the data being returned.