系统中出现过几次connection holder is null问题,有的已解决,有的未解决,记录如下。首先说先druid连接池的实现:
- DruidPooledConnection是一个静态代理,持有ConnectionHolder, connection Holder里持有具体的connection对象, 在执行druidPooledConnection的所有和数据库相关方法时,都会先调用checkState()判断connection holder是否为null,如果是null就抛connection holder is null的异常。
- 那connection holder是什么时候赋值以及什么时候置成null的?
在Datasource.getConnection()获取连接的时候,是从池里取出缓存的connection holder对象,druid是用一个数组缓存connection holder对象,每次都是从最后一个取,还的时候也是放到最后,这样保证位于数组最后的连接会经常处于使用状态,当然这中间会有锁的使用以及池里没线程了通知任务线程去创建新连接。
Datasource.getConnection()从池里拿出connection holder后,然后new一个druidPooledConnection去包装connection holder,所有每次看到都是不同的druidPooledConnection对象。
第一次:系统中事务执行时间过长,超过60秒,后面导致有的请求会报connection holder is null。
- 拿出来的connection holder肯定不为null,项目中报connection holder is null,说明是在使用过程中connection holder被置成null了,很大概率是被别的线程置成null了,因为本线程只有在事务提交后还连接的时候才置null,在github issue上,作者也反复强调连接不要跨线程使用。而druid真的就有跨线程操作连接的地方,就是remove abandoned connection功能,这个功能是为了回收长时间还没还到池里的连接,多长时间看你设置,而我们项目设置的60秒没还就强制回收,这样就会报上面的错误了。
- 建议在生产环境关闭remove abandoned功能,如果数据库负载不重的话,可以开启testOnBorrow。testWhileIde不建议开,因为并发请求多的话,数组后面的连接都不是idle状态,开没开testWhileIdle没啥区别。
第二次系统中有的事务长时间未提交,DBA会把这个连接kill掉,后面请求会报conneciton holder is null
- 为什么有长时间未提交的事务,这个问题还没找到原因,从Mysql的innodb_trx和lock表里没看到有价值线索,后面想跟踪事务和连接来看看有没有收获。
- 连接被kill了,应用端会报这样的异常:
- Can not read response from server. Expected to read 4 bytes, read 0 bytes before connection was unexpectedly lost.
- com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure. The last packet successfully received from the server was 20,840 milliseconds ago. The last packet sent successfully to the server was 20,840 milliseconds ago.
- connection holder is null
前面2个异常很好理解,tcp连接断了,应用端读不到数据报错,然后druid捕获到异常,要去判断这个异常是可恢复异常还是不可恢复异常。因为站在连接池的角度来说,数据库抛异常太普遍了,可能是唯一索引重复也可能是连接断了,对于不同的异常处理方式也是不一样的,唯一索引重复需要调用connection.rollback(),然后再把连接还到池里,因为这个连接还是好的,不影响下次继续使用。而连接断了,则要把这个连接踢出去,druid用了ExceptionSorter来判断这个异常是不是不可恢复异常,在转换异常的时候要用当前连接获取数据库的metadata,而当前连接已经断了,所以报connection holder is null。但是这个connection holder is null只会报一次,和项目中大量报connection holder is null不是一个东西,目前还没找到原因。而这个问题在本地却重现不了。PS:数据库有一个设置 rollback_on_timeout,默认是off,这个值是说当事务超时(如超过50秒还没获取到锁),默认off是回滚最后一条sql语句,on是回滚整个事务。这个值一般不需要设置成on,交由应用去处理,应用在获取不到 can't acquire lock的时候,一般会去调connection.rollback(),当然前提是要你的应用开启事务。
下方二维码关注我
互联网草根,坚持分享技术、创业、产品等心得和总结~