“超时expired"只使用using语句的代码异常

本文关键字:using 语句 异常 代码 expired 超时 quot | 更新日期: 2023-09-27 18:03:16

我有一个多线程应用程序,通过Linq to SQL与SQL服务器对话。当线程数人为地保持在8时,应用程序在四核(Intel I-7)机器上运行良好:

            Parallel.ForEach(allIds, 
                    new ParallelOptions { MaxDegreeOfParallelism = 8 },
                    x => DoTheWork(x));

当线程数留给系统决定时:

                Parallel.ForEach(allIds, x => DoTheWork(x));

运行一段时间后,我得到以下异常:

超时过期。的超时时间来自池的连接。这可能是因为所有的池

连接正在使用,达到最大池大小。
在我的应用程序中只有两种模式调用SQL:

:

    using (var dc = new MyDataContext())
    {
        //do stuff
        dc.SafeSubmitChanges();
    }
第二:

        using (var dc = new MyDataContext())
        {
            //do some other stuff
            DoStuff(dc);
        }
.....
    private void DoStuff(DataContext dc)
    {
       //do stuff
       dc.SafeSubmitChanges();
    }

我决定通过这种形式的逻辑来限制调用:

public static class DataContextExtention
{
    public const int SQL_WAIT_PERIOD = 5000;
    public static void SafeSubmitChanges(this DataContext dc)
    {
        try
        {
            dc.SubmitChanges();
        }
        catch (Exception e)
        {
            if (e.Message ==
                "Timeout expired.  The timeout period elapsed prior to obtaining a connection from the pool.  This may have occurred because all pooled connections were in use and max pool size was reached.")
            {
                System.Data.SqlClient.SqlConnection.ClearAllPools();
                System.Threading.Thread.Sleep(SQL_WAIT_PERIOD);
                dc.SafeSubmitChanges();
            }
            else
            {
                throw;
            }
        }
    }
}

这完全没有区别。一旦应用抛出第一个此类异常,应用中的所有随机位置(甚至是与SQL server无关的代码行)都会开始抛出此异常。

第1题:严格地使用using语句难道不应该防止这种情况吗?

Q2:问题是什么,我该如何解决这个问题?

注:大约有250,000个id。我也在MaxDegreeOfParallelism = 16测试,我得到同样的异常。

“超时expired"只使用using语句的代码异常

我想这取决于allIds中有多少项。如果Parallel.ForEach创建了太多的并行并发任务,可能是每个任务都试图打开与数据库的连接(并行),从而耗尽连接池,使其无法为所有请求新连接的并发任务提供连接。

如果满足连接池请求所需的时间超过超时时间,则该错误消息是有意义的。因此,当您设置MaxDegreeOfParallelism = 8时,您的并发任务不超过8个,因此从池中"签出"的连接不超过8个。在任务完成之前(Parallel.ForEach现在有一个可用的插槽来运行新任务),连接被返回到连接池,这样当Parallel.ForEach运行下一个项目时,连接池可以满足下一个连接请求,因此当您人为地限制并发性时,您就不会遇到这个问题。

编辑1

@hatch上面的建议是正确的-增加池的大小。然而,有一个警告。瓶颈可能不是真正的计算能力,而是数据库活动。我怀疑(诚然是猜测)正在发生的事情是,当与数据库通信时,线程不能做太多事情,并且被阻塞(或切换到另一个任务)。因此,线程池看到有更多的任务挂起,但CPU没有被利用(因为未完成的IO操作),因此决定为可用的CPU空闲承担更多的任务。当然,这只会使瓶颈更加饱和,并回到起点。因此,即使增加了连接池的大小,在池大小与任务列表一样大之前,您可能会一直遇到瓶颈。因此,您可能实际上希望具有有限的并行性,以便它永远不会耗尽线程池(并根据DB负载等通过使线程池变大/变小进行微调)。

判断上述情况是否成立的一种方法是,看看为什么连接花了这么长时间,而且没有返回到池中。例如,分析是否有db争用导致所有连接变慢。如果是这样,更多的并行化不会给你带来任何好处(事实上,这会让事情变得更糟)。

我认为以下可能会有所帮助,在我使用Oracle的经验中,DB连接池之前已经引起了我的问题。所以我认为可能有类似的问题与SQL Server连接池。有时,知道默认连接设置并查看DB上的连接活动是很好的信息。

如果您使用的是Sql Server 8,默认的Sql连接池是100。默认超时时间为15秒。我想有SQL管理跟踪你有多少连接,而运行应用程序,看看你是否把负载放在数据库服务器。也许还可以添加一些性能计数器。因为这看起来像一个SQL Server异常,我会得到一些指标,看看发生了什么。您还可以使用智能来帮助查看数据库活动。

Intellitrace Link: http://www.dotnetcurry.com/showarticle.aspx?ID=943

Sql Server 2008 Connection Pool Link: http://msdn.microsoft.com/en-us/library/8xx3tyca(v=vs.110).aspx

性能计数器链接:http://msdn.microsoft.com/en-us/library/ms254503(v=vs.110).aspx

我可能在这里偏离了目标,但我想知道这个问题是否不是作为连接池这个事实的副作用引起的(从这里开始,强调我的):

当连接池启用时,如果发生超时错误或其他登录错误,将抛出异常,随后的连接尝试将在接下来的5秒,即"阻塞期"内失败。如果应用程序试图在阻塞期间内连接,则将再次抛出第一个异常。阻塞周期结束后,如果再次出现故障,则会产生一个新的阻塞周期,该阻塞周期是前一个阻塞周期的两倍,最长不超过1分钟。

  • 所以,换句话说,并不是连接耗尽,而是在一个或多个并行操作上出现了故障,可能是因为可怜的表在并行写的压力下崩溃了——您是否对数据库端发生的情况进行了分析,以查看在操作期间表上是否存在争用问题?

  • 这可能会导致其他连接请求由于上面描述的"惩罚"而开始备份;因此,异常,一旦你开始得到一个,你的SafeSubmit方法只会使事情变得更糟,因为它不断重试一个已经被取消的操作。

  • 这个解释也将极大地支持这样一个观点,即真正的瓶颈是数据库,也许用无限并行IO来锤击一个表不是一个好主意;最好根据数据库所能承受的特性(不同的硬件可能会有所不同)来测量并得出最大DOP

另外,关于你的第一个问题,using只保证你的DataContext对象在超出作用域时将自动变成Dispose() d,所以它根本不是为了在这种情况下进行保护而设计的——它只是

的语法糖
try
{
    var dc = new DataContext();
    //do stuff with dc
}
finally
{
    dc.Dispose();
}

,在这种情况下,这并不能防止有(太多)多个datacontext当前试图同时连接到数据库。

您确定您没有面临连接泄漏吗?请在此链接查看接受的答案

另外,您是否已经设置了MultipleActiveResultSets = true ?

从MSDN:

当为true时,应用程序可以维护多个活动结果集(火星)。当为false时,应用程序必须处理或取消所有结果在执行任何其他批处理之前,从一个批处理中设置连接。可识别值为true和false。

有关详细信息,请参见多活动结果集(MARS)。