ASP.NET MVC+EF6+MySQL下FirstOrDefault触发集合已修改异常求助
问题本质
这个异常的根本原因是Connector/NET 8.0.19版本中FailoverManager类的共享集合没有实现线程安全控制,多线程并发触发数据库连接打开操作时,会出现集合被修改同时被遍历的冲突。爬虫请求通常并发度更高,更容易触发该类线程安全问题。
可行解决方案(按落地优先级排序)
方案1:绕过内置多主机负载逻辑(推荐快速修复)
放弃Connector/NET内置的多主机负载均衡能力,改用HAProxy、LVS等第三方负载均衡组件代理数据库请求,应用层仅配置单个代理节点的连接字符串,完全绕过有问题的FailoverManager代码。修改后的连接字符串格式如下:
Server=负载均衡代理地址; Database=myDataBase; Uid=myUsername; Pwd=myPassword;
该方案无需修改业务代码,稳定性最高,适合快速上线解决问题。
方案2:加全局锁控制连接并发
如果暂时无法部署负载均衡组件,可以给MySQL连接的打开操作加全局锁,避免多线程同时触发连接字符串解析逻辑,实现步骤如下:
- 自定义EF连接拦截器
public class MysqlConnectionLockInterceptor : DbConnectionInterceptor { private static readonly object _connLock = new object(); public override void Opening(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { Monitor.Enter(_connLock); base.Opening(connection, interceptionContext); } public override void Opened(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { Monitor.Exit(_connLock); base.Opened(connection, interceptionContext); } public override void OpenFailed(DbConnection connection, DbConnectionErrorInterceptionContext interceptionContext) { if (Monitor.IsEntered(_connLock)) { Monitor.Exit(_connLock); } base.OpenFailed(connection, interceptionContext); } }
- 应用启动时注册拦截器
DbInterception.Add(new MysqlConnectionLockInterceptor());
该方案仅会轻微影响数据库连接建立的并发性能,对普通流量规模的站点完全可接受。
方案3:调整Connector/NET版本
当前使用的8.0.19版本仍存在未修复的FailoverManager线程安全bug,可以尝试升级到最新的8.0.x稳定版,或者降级到经过验证无该问题的历史版本,降级前需做好EF6兼容性测试。
方案4:自定义修复驱动代码
如果对Connector/NET源码熟悉,可以将FailoverManager相关代码拷贝到本地项目,给涉及共享集合读写的位置补充读写锁,编译后替换官方的MySQL.Data.dll即可。
内容的提问来源于stack exchange,提问作者Hooman Bahreini
相关产品推荐
相关产品推荐

