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

EF Core搭配MySQL并发访问错误排查与解决咨询

问题与解决方案

问题描述

基于.NET 7开发Windows MAUI + Blazor原生应用,通过HotChocolate GraphQL后端每5秒查询MySQL 8 InnoDB数据库的ChangeSets表,按主键ChangeSetIndex倒序获取最新数据。多数请求失败,随机触发两种错误:

  1. 错误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. 错误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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 11:59:56