我有一个多线程应用程序,通过Linq到Sql与SQL服务器通信。当线程数被人为地保持在8时,应用程序在四核(Intel I-7)机器上正常运行:
Parallel.ForEach(allIds,
new ParallelOptions { MaxDegreeOfParallelism = 8 },
x => DoTheWork(x));
当线程数留给系统决定时:
Parallel.ForEach(allIds, x => DoTheWork(x));
运行一段时间后,我得到以下异常:
超时已过期。在获得a之前经过了超时时间 从游泳池连接。这可能是因为所有人都集中了 正在使用连接并达到最大池大小。
我的应用程序中只有两种模式用于调用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服务器无关的代码行)都会开始抛出此异常。
第一季度:是不是虔诚地使用声明来反对这种情况呢?Q2:出了什么问题,如何解决这个问题?
注意:大约有250,000个ID。我也在MaxDegreeOfParallelism = 16
进行了测试,我得到了相同的例外。
答案 0 :(得分:3)
我认为以下内容可能有所帮助,根据我使用Oracle的经验,数据库连接池之前已经给我带来了问题。所以我认为SQL Server连接池可能存在类似的问题。有时知道默认连接设置并查看数据库上的连接活动是很好的信息。
如果您使用的是Sql Server 8,则默认SQL连接池为100.默认超时为15秒。我希望让SQL管理员跟踪您在运行应用程序时所做的连接数量,并查看您在数据库服务器上的加载情况。也许还要添加一些性能计数器。由于这看起来像SQL Server异常,我会得到一些指标来看看发生了什么。您还可以使用intellitrace来帮助查看DB活动。
Intellitrace链接:http://www.dotnetcurry.com/showarticle.aspx?ID=943
Sql Server 2008连接池链接:http://msdn.microsoft.com/en-us/library/8xx3tyca(v=vs.110).aspx
效果计数器链接:http://msdn.microsoft.com/en-us/library/ms254503(v=vs.110).aspx
答案 1 :(得分:2)
我认为这取决于allIds
中有多少项目。如果Parallel.ForEach
创建了太多并行并发任务,则可能每个人都尝试打开与数据库的连接(并行),从而耗尽连接池并使其无法提供与请求新连接的所有并发任务的连接连接。
如果满足连接池请求的时间超过超时,则该错误消息将有意义。因此,当您设置MaxDegreeOfParallelism = 8
时,您的并发任务不超过8个,因此不超过8个连接"签出"从游泳池。在任务完成之前(并且Parallel.ForEach
现在有一个可用的插槽来运行新任务),连接将返回到池中,这样当Parallel.ForEach
运行下一个项目时,连接池可以满足下一个请求对于连接,因此当您人为地限制并发时,您不会遇到此问题。
@ hatched的建议在正确的轨道上 - 增加游泳池大小。但是,有一个警告。您的瓶颈可能不是计算能力,而是数据库活动。我怀疑(正当猜测)正在发生的事情是,在与数据库交谈时,线程无法做很多事情并且被阻止(或切换到另一个任务)。因此线程池看到有更多任务待处理,但CPU未被利用(因为未完成的IO操作),因此决定为可用的CPU冗余承担更多任务。这当然只是使瓶颈更加饱和并回到原点。因此,即使您增加连接池大小,在您的池大小与任务列表一样大之前,您可能会继续遇到问题。因此,您实际上可能希望具有有限的并行性,以便它永远不会耗尽线程池(并通过使线程池更大/更小来进行微调,具体取决于数据库负载等)。
尝试找出上述情况是否属实的一种方法是查看为什么连接花费这么长时间并且没有返回到池中。即分析以查看是否存在使所有连接减慢的数据库争用。如果是这样,更多的并行化对你没有任何好处(事实上,这将使事情变得更糟)。
答案 2 :(得分:2)
我可能会离开这里的目标,但我想知道这个问题是不是因为这个关于连接池的事实的副作用(取自here,强调我的):
启用连接池时,如果发生超时错误或其他登录错误,将引发异常,后续连接尝试将在接下来的五秒内失败,即“阻止期”。如果应用程序尝试在阻塞期内连接,则将再次抛出第一个异常。 阻止期结束后的后续失败将导致新的阻止时段为前一个阻塞时段的两倍,最多为一分钟。
换句话说,并不是说你的连接本身已经用完了,而是因为一个或多个并行操作失败了,可能是因为糟糕的桌子在并行写入的压力下崩溃了 - 你有没有想过在数据库端发生了什么,看看在操作过程中是否有任何争用问题?
由于上述“惩罚”,这可能导致其他连接请求开始备份;因此异常,一旦你开始得到一个,你的SafeSubmit
方法只能让事情变得更糟,因为它一直在重试已经禁止的操作。
这个解释也会大大支持这样一个想法,即这里真正的瓶颈是数据库,并且尝试使用无界并行IO敲击表并不是一个好主意;根据数据库可以承受的特性(对于不同的硬件可能有所不同)来测量和提出最大DOP更好
另外,关于您的第一个问题,using
仅保证DataContext
对象在超出范围时会自动Dispose()
d,因此根本不设计在这种情况下保护 - 所有这一切都是
try
{
var dc = new DataContext();
//do stuff with dc
}
finally
{
dc.Dispose();
}
并且在这种情况下,并不是防止(太多)DataContexts当前正在尝试同时连接到数据库。
答案 3 :(得分:0)
您确定没有面对连接泄漏吗?请在this link
查看已接受的答案此外,您是否已设置MultipleActiveResultSets = true
?
来自MSDN:
如果为true,则应用程序可以维护多个活动结果集 (火星)。如果为false,则应用程序必须处理或取消所有结果 在它可以执行任何其他批次之前从一个批次设置 连接。认可的价值观是真实的。
有关详细信息,请参阅Multiple Active Result Sets (MARS)。