DB ConnectionState = Open 但 context.SaveChanges 抛出“连接中断”异常

Posted

技术标签:

【中文标题】DB ConnectionState = Open 但 context.SaveChanges 抛出“连接中断”异常【英文标题】:DB ConnectionState = Open but context.SaveChanges throws "connection broken" exception 【发布时间】:2016-11-10 02:20:17 【问题描述】:

在我的服务中,我有一个后台线程,它尽最大努力保存某些实体类型的对象流。代码大致如下:

        while (AllowRun)
        
            try
            
                using (DbContext context = GetNewDbContext())
                
                    while (AllowRun && context.GetConnection().State == ConnectionState.Open)
                    
                        TEntity entity = null;
                        try
                        
                            while (pendingLogs.Count > 0)
                            
                                lock (pendingLogs)
                                
                                    entity = null;
                                    if (pendingLogs.Count > 0)
                                    
                                        entity = pendingLogs[0];
                                        pendingLogs.RemoveAt(0);
                                    
                                

                                if (entity != null)
                                
                                    context.Entities.Add(entity);
                                
                            
                            context.SaveChanges();
                        
                        catch (Exception e)
                        
                            // (1)
                            // Log exception and continue execution
                        
                    
                
            
            catch (Exception e)
            
               // Log context initialization failure and continue execution
            
        

(这主要是实际代码,我省略了一些不相关的部分,它们试图将弹出的对象保留在内存中,直到我们能够在(1) 块捕获异常时再次将内容保存到数据库)

因此,本质上,存在一个无限循环,试图从某个列表中读取项目并将它们保存到 Db。如果我们检测到与 DB 的连接由于某种原因失败,它只会尝试重新打开它并继续。问题是有时(到目前为止我还没有弄清楚如何重现它),上面的代码在调用context.SaveChanges() 时开始产生以下异常(在(1) 块中捕获):

System.Data.EntityException: An error occurred while starting a transaction on the provider connection. See the inner exception for details. ---> 
System.InvalidOperationException: The requested operation cannot be completed because the connection has been broken.

错误已记录,但当执行返回到context.GetConnection().State == ConnectionState.Open 检查时,它的计算结果为真。因此,当上下文报告其数据库连接已打开时,我们处于一种状态,但我们无法针对该上下文运行查询。重新启动服务可以解决问题(以及在调试器中弄乱AllowRun 变量以强制重新创建上下文)。所以问题是因为我不能信任上下文的连接状态,我如何验证我可以对 DB 运行查询?

另外,有没有一种干净的方法可以确定连接不处于“健康”状态?我的意思是,EntityException 本身并不表示我应该重置连接,只有当它的 InnerException 是 InvalidOperationException 并带有一些特定的消息时,是的,是时候重置它了。但是,现在我猜想在其他情况下 ConnectionState 指示一切正常,但我无法查询 DB。我可以主动抓住那些,而不是等到它开始咬我吗?

【问题讨论】:

你能提供详细的堆栈跟踪吗?似乎某些事务弄乱了正在进行的 EF 连接或遇到了连接超时问题(如果应用分布式事务,可能来自 MSDTC)。 您忽略的最有可能不相关的部分是真正相关的。总而言之,在此操作的生命周期内无需保持连接。您使用连接的唯一方法是 SaveChanges 调用(如果您的示例重复调用它,即使 pendingLogs 为空)。只需在pendingLogs 不为空时创建新的上下文(连接将最常从池中返回)并在SaveChanges 之后处置(连接将返回到池中)。还要检查您的事务是否升级为分布式以及为什么(也许您在一个事务中调用多个数据库等)。 为什么你首先会得到一个打开连接的上下文? @TetsuyaYamamoto,一旦我在日志中找到它,我会发布完整的堆栈跟踪,这个问题不会经常发生(我已经看过两次,包括一个导致我写这个)。 @GertArnold,这是一个有趣的问题。出于某种原因,我们的上下文创建方法总是使用ctx = new DbContext(...); ctx.GetConnection().Open()。为什么会这样,我只能猜测:代码早于我,但我的猜测是我们将能够在一个地方处理连接失败。 【参考方案1】:

什么是日志频率?

如果此循环花费的时间超过连接超时,则在执行 savechanges 时连接关闭。

while (pendingLogs.Count > 0)

     lock (pendingLogs)
     
          entity = null;
          if (pendingLogs.Count > 0)
          
             entity = pendingLogs[0];
             pendingLogs.RemoveAt(0);
          
     

     if (entity != null)
     
          context.Entities.Add(entity);
     

context.SaveChanges();

【讨论】:

“连接超时”是建立连接的时间限制。一旦建立,就没有超时保持打开状态。我错了吗?至少我从未见过已建立的 SQL 连接超时(当然查询超时除外)。【参考方案2】:

根据我从事类似服务的经验,直到 using 块结束时才会进行垃圾收集。

如果有很多 Pending 日志要写入,这可能会占用大量内存,但我也猜想它可能会饿死 dbConnection 池。

您可以使用 RedGate ANTS 或类似工具分析内存使用情况,并使用 *** 问题中的以下脚本检查打开的 dbConnections:how to see active SQL Server connections?

SELECT 
    DB_NAME(dbid) as DBName, 
    COUNT(dbid) as NumberOfConnections,
    loginame as LoginName
FROM
    sys.sysprocesses
WHERE 
    dbid > 0
GROUP BY 
    dbid, loginame

;

我认为尽可能频繁地释放上下文是一种很好的做法,以便让 GC 进行清理更改,因此您可以将循环重写为:

while (AllowRun)
    
        try
        
            while (pendingLogs.Count > 0)
            
                using (DbContext context = GetNewDbContext())
                
                    while (AllowRun && context.GetConnection().State == ConnectionState.Open)
                    
                        TEntity entity = null;
                        try
                        

                            lock (pendingLogs)
                            
                                entity = null;
                                if (pendingLogs.Count > 0)
                                
                                    entity = pendingLogs[0];
                                    pendingLogs.RemoveAt(0);
                                
                            

                            if (entity != null)
                            
                                context.Entities.Add(entity);
                                context.SaveChanges();
                            
                                                
                        catch (Exception e)
                        
                            // (1)
                            // Log exception and continue execution
                        
                    
                
            
        
        catch (Exception e)
        
           // Log context initialization failure and continue execution
        
    

【讨论】:

【参考方案3】:

我建议通过以下网址: 当 sql 查询运行时间过长时,通常会抛出 Timeout Expired。

听起来像是 SQL 作业正在运行,备份?这可能是锁定表或重新启动服务。

ADONET async execution - connection broken error

【讨论】:

以上是关于DB ConnectionState = Open 但 context.SaveChanges 抛出“连接中断”异常的主要内容,如果未能解决你的问题,请参考以下文章

sql参数更改错误

按日期对数据进行分组并将DateTime转换为Double

sql SqlBulkCopy

SQL连接数据库

如何使用 StreamProvider 检查 Firestore 快照流的 ConnectionState?

AsyncSnapshot 状态始终为 connectionState.waiting