好大一个坑: EF Core 异步读取大字符串字段比同步慢100多倍
这两天遇到一个奇怪的问题,通过 EF/EF Core 查询数据库速度奇慢,先是在传统的 ASP.NET 项目中遇到(用的是EF6.0),后来将该项目迁移至 ASP.NET Core 也是同样的问题(用的是EF Core 2.2.2)。
问题触发的条件是所查询的字段中存储了很大的字符串(有400多万个字符),查询耗时竟然要40s左右(对,是40秒),CPU消耗也很高,2核CPU消耗50%-80%左右,而换成 Dapper 则没这个问题。
通过 EF Core 的 Debug 日志跟踪发现,耗时发生在执行完 DbCommand 与 dispose DbDataReader 之间:
2019-02-23 15:46:27.026 [Information] Executed DbCommand ("4"ms) [Parameters=[""], CommandType='Text', CommandTimeout='30']" 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(),结果却让人大吃一惊,多次测试,查询耗时在 100-500 ms 之间,快了 100 多倍。
更新
触发这个问题有 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.
- 用java和oracle实现BLOB字段的字符串读取【转】
- 装饰者模式的学习(c#) EF SaveChanges() 报错(转载) C# 四舍五入 保留两位小数(转载) DataGridView样式生成器使用说明 MSSQL如何将查询结果拼接成字符串 快递查询 C# 通过smtp直接发送邮件 C# 带参访问接口,WebClient方式 C# 发送手机短信 文件 日志 写入 与读取
- 关于mybatis读取数据库字段text类型时,读出数据为地址,并不是字符串的问题
- iOS block 的 同步执行和异步执行详解 加 属性字符串 设置
- EF中一对多的关系中,用单字段保存ID拼接字符串
- ASP.NET Core使用HttpClient的同步和异步请求
- D3.js中强制异步文件读取同步的几种方法
- OPC工作记录整理——第六篇(同步读取和异步读取)
- asp.net core 系列 22 EF(连接字符串,连接复原,DbContext)
- EF调用存储过程查询表中的部分字段,报数据读取器与指定的“AdventureWorksDWModel.Student”不兼容。某个类型为“Age”的成员在同名的数据读取器中没有对应的列。
- 同步、异步读取股票行情(源代码)
- SerialPort同步和异步数据读取
- 用java和oracle实现BLOB字段的字符串读取【转】
- nodejs文件系统fs模块小小补充(同步读取和异步读取)
- 利用BenchmarkDotNet 测试 .Net Core API 同步和异步方法性能
- C#.net读取Excel表中的数据时,有些字段内容(字符串、数字)读取不到的解决办法
- nodejs异步读取文件与同步读取文件的区别
- ASP.NET Core教程【三】实体字段属性、链接标签、并发数据异常、文件上传及读取
- EF-按字段读取
- node批量读取文件时异步变同步的方法分享