“超时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
测试,我得到同样的异常。
我想这取决于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
?
当为true时,应用程序可以维护多个活动结果集(火星)。当为false时,应用程序必须处理或取消所有结果在执行任何其他批处理之前,从一个批处理中设置连接。可识别值为true和false。
有关详细信息,请参见多活动结果集(MARS)。