日期:2014-05-16  浏览次数:20548 次

数据库连接池死锁的原因和处理方法

数据库连接池是java软件常用的组件,但对数据库连接池不正确的使用会造成软件的死锁。
死锁的产生原理是:存在线程同时占有两个以上的连接对象的情况。

比如某连接池中有2个连接对象。
有两个线程分别占用了一个,又分别来申请另一个。这时由于连接池空,而两个线程本身都卡在连接池上,不可能释放原来占有的连接对象,这时产生死锁。

连接池死锁,会导致软件和数据库相关的部份无响应。比如登陆功能,会用数据库做用户校验,发生连接池死锁后,登陆功能会无法使用(点登陆按钮后,长时间无相应)。

连接池死锁的问题可以参看以下文章:

http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=%2Frzatz%2F51%2Fprogram%2Fdatapool.htm

解决方法:一个线程中同时只允许使用一个连接对象。

一段数据库逻辑通常只需要一个连接对象,因此在一个方法中实现这点是比较容易的。但是当发生如下情形时则比较麻烦:
有两个方法A和B,A和B都要使用数据库连接,而A在事务中调用了B。

为了解决这个问题有如下两种办法:

一是,将连接对象放在线程的ThreadLocal中。这样A和B即可共享连接对象了。但这种方法需要考虑嵌套事务的问题。数据库是不允许嵌套事务的。因此,要么避免嵌套事务,要么自动合并事务。
注意:采用传参的形式也可以做到这点,但只能就问题解决问题,不是框架层面的解决方案,也要处理嵌套事务的问题。

二是,直接人为在代码上保证一个线程不同时占有两个连接对象。幸好这样的情况从经验上看并不多,只要找到这样的方法,想办法拆分一下逻辑,将调用的部份从事务中拿出来即可。

???? 还有一种古怪的办法,就是新起一个线程来调用B。如果A对B的返回值有依赖关系,这种办法是行不通的,不推荐使用。

??? 有人认为只要给连接池设置一个超时就行了。可是,这样做并不恰当。因为,连接是应该可以共享的,如果暂时,连接池内没有连接可用,应该让申请者等待可用的连接,而不是抛出超时异常,这样会导致申请者整个过程的失败。

?

?

2012-02-02

?

新发现了一种由连接池引起的死锁情况,并不是由一个线程占用两个以上的连接引起的,是因为线程取得连接后被锁卡住不能归还引起的方式如下:

?

连接池中有一个连接对象。

?

A线程:

取得连接对象

开始事务

synchronized(cache){

??? 数据库代码

}

提交

关连接

?

B线程:

synchronized(cache){

?? 取得连接,操作数据库。

}

?

A线程执行卡在同步块外,此时连接池为空。

B线程进入同步块,在取得连接时卡在连接池上。

?

?

?

?

1 楼 eyesmore 2012-04-03  
这种问题确实让人很揪心。按道理来讲,连接池的连接用光了,业务线程的状态应该是WAITING才对呀(默认策略下),不会是BLOCKED的。 但实际生产环境如果出了问题,业务线程的状态的确是BLOCKED。能否详细解说下 进一步导致BLOCKED原因,实例丰富点,期待大师解说~~
2 楼 eyesmore 2012-04-03  
"比如某连接池中有2个连接对象。
有两个线程分别占用了一个,又分别来申请另一个。这时由于连接池空,而两个线程本身都卡在连接池上,不可能释放原来占有的连接对象,这时产生死锁。 "

这句话应该是错误的,依据我的经验,这个时候不会死锁,只是两个线程都WAITING(如果连接池配置的时候当连接不够用的时候,等待的话)。你可以做个简单的实验,然后用jstack查看线程情况。
3 楼 gdpglc 2012-04-06  
我不是大师, 这样称呼的确不当,会让别人笑话. 你要努力,也许几年后要比我强.

另外,似乎你对死锁有误解. 它不是指线程的状态. 以下是死锁的定义:

http://baike.baidu.com/view/121723.htm

看懂死锁的定义后, 应该能明白我写的内容.