EF Core搭配MySQL并发访问错误排查与解决咨询
问题与解决方案
问题描述
基于.NET 7开发Windows MAUI + Blazor原生应用,通过HotChocolate GraphQL后端每5秒查询MySQL 8 InnoDB数据库的ChangeSets表,按主键ChangeSetIndex倒序获取最新数据。多数请求失败,随机触发两种错误:
- 错误1:
"An item with the same key has already been added. Key: server=host;port=3306;database=db;user id=user;password=pwd"(Key为数据库连接字符串) - 错误2:
"Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct."
调整DbContext实例化方式与生命周期后问题仍存在,需进一步排查解决。
核心原因分析
从堆栈跟踪和代码来看,问题根源在于**SqlDataProvider中静态的_options字段在多线程环境下被并发初始化**:
SqlDataProvider被注册为Transient,每次请求都会创建新实例- 静态字段
_options的初始化使用_options ??= ...,该操作并非线程安全,多个线程会同时执行DbContextOptionsBuilder的创建逻辑 - MySQL连接池管理器内部使用非线程安全的
Dictionary存储连接池,并发添加相同连接字符串的键时,就会触发重复键错误或集合状态损坏的异常
解决方案
1. 正确注册DbContext到依赖注入容器
在Program.cs中移除手动创建DbContextOptions的逻辑,改用EF Core官方的AddDbContext方法注册,让DI容器管理DbContextOptions和DbContext的生命周期:
// Program.cs中添加DbContext注册 var connectionString = builder.Configuration.GetConnectionString("RemoteDatabase") ?? Constants.TestDatabaseConnectionString; builder.Services.AddDbContext<SqlContext>(options => options.UseMySQL(connectionString));
2. 修改SqlDataProvider,移除静态字段并注入DbContextOptions
删除SqlDataProvider中的静态_options字段,改为通过构造函数注入DbContextOptions<SqlContext>,确保每个实例使用安全的上下文配置:
public class SqlDataProvider : IDataProvider { public bool IsInitialized { get; set; } public event Action? DatabaseInitialized; private readonly FileService _fileService; private readonly ExceptionHandler _exceptionHandler; private readonly DbContextOptions<SqlContext> _dbContextOptions; // 构造函数注入DbContextOptions public SqlDataProvider(FileService fileService, ExceptionHandler exceptionHandler, DbContextOptions<SqlContext> dbContextOptions) { _fileService = fileService; _exceptionHandler = exceptionHandler; _dbContextOptions = dbContextOptions; } public SqlContext GetContext() { return new SqlContext(_dbContextOptions); } public IUnitOfWork CreateUnitOfWork(string userId = Constants.DefaultUserId) { var context = GetContext(); return new SqlUnitOfWork(context, _fileService, _exceptionHandler, userId!); } }
3. 确保DbContext的生命周期隔离
保持UnitOfWork的using用法,确保每个数据库操作使用独立的DbContext实例(DbContext本身线程不安全,不可共享):
// Query.cs中的现有用法是正确的,保留using隔离上下文 using var unitOfWork = (SqlUnitOfWork)_dataProvider.CreateUnitOfWork();
额外优化建议
- 确认MySQL连接器版本:使用最新的
MySql.EntityFrameworkCore或Pomelo.EntityFrameworkCore.MySql版本,旧版本可能存在连接池线程安全问题 - 调整连接池配置:在连接字符串中添加连接池参数(如
Max Pool Size=100;Min Pool Size=10),避免连接耗尽 - 减少查询频率:将5秒一次的查询改为基于数据库变更通知(如MySQL的Binlog监听)或长轮询,降低并发请求压力
内容的提问来源于stack exchange,提问作者Movsar Bekaev
相关产品推荐
相关产品推荐

