在应用端经过mybatis的interceptor自定义Plugin拦截Executor, 统计输出sql的执行耗时。html
今天生产发生一个很奇怪的问题: 莫名其妙卡顿15分钟+,其后正常返回sql正常结果! 使用druid版本是1.0.2。。。。。java
统计发现:mysql
推测是不是在getConnection过程当中出现了等待。但因获取链接时的日志过少找不到具体的缘由!git
网上的相似问题说的缘由是Druid在从链接池中获取的链接(代码详见:DruidDataSource的getConnectionDirect),在开启testWhileIdle时,若是活跃时间距离当前比较久,那么将验证该链接是否有效,无效会discardConnection并再次去pool内获取。github
这个过程可能被数据库层面的防火墙策略已经关闭该链接,而客户端还傻傻的发sql验证直到超时。。。。spring
在该测试链接的方法中有一个查询超时超时时间:validationQueryTimeout。测试中是-1, 执行的是默认时间。。。。这个默认时间没有找到!! (有找到MappedStatementConfig由SqlMapConfiguration的defaultStatementTimeout)sql
应用与数据库间的timeout层级实例:
转帖: 深刻理解JDBC的超时设置数据库
上层的timeout依赖于下层的timeout,只有下层的timeout无误时,上层的timeout才能确保正常。eg. 当socket timeout出现问题时,上层的statement timeout和transaction timeout都将失效。
statement timeout 没法处理网络链接失败时的超时,它能作的仅仅是限制statement的操做时间;网络链接失败时的timeout必须交由JDBC来处理;JDBC的socket timeout会受到操做系统socket timeout设置的影响。api
Transaction Timeout: 通常存在于框架(Spring, EJB)或应用级。spring在配置事务切面的时候能够配置timeout。服务器
Statement Timeout: 经过调用JDBC的java.sql.Statement.setQueryTimeout(int timeout) API进行设置。 以myBatis为例,statement timeout的默认值能够经过sql-map-config.xml中的defaultStatementTimeout 属性进行设置。同时,也能够设置sqlmap中select,insert,update标签的timeout属性,从而对不一样sql语句的超时时间进行独立的配置。
JDBC的statement timeout: 经过调用Connection的createStatement()方法建立statement来executeQuery时,会注册一个 TimerTask ->CancelTask 用来给Timer -> ConnectionImpl.getCancelTimer()来处理timeout事项:发送 "KILL QUERY” 。 代码详见: StatementImpl 及 ConnectionImpl
JDBC的socket timeout:
因为TCP/IP的结构缘由,socket没有办法探测到网络错误,所以应用也没法主动发现数据库链接断开。若是没有设置socket timeout的话,应用在数据库返回结果前会无期限地等下去,这种链接被称为dead connection。 为了不dead connections,socket必需要有超时配置。socket timeout能够经过JDBC设置,socket timeout可以避免应用在发生网络错误时产生无休止等待的状况,缩短服务失效的时间。不推荐使用socket timeout来限制statement的执行时长,所以socket timeout的值必需要高于statement timeout,不然,socket timeout将会先生效,这样statement timeout就变得毫无心义,也没法生效。
eg. jdbc:mysql://xxx.xx.xxx.xxx:3306/database?connectTimeout=60000&socketTimeout=60000
操做系统的socket timeout配置
在该文章中说Statement.setQueryTimeout : 若是设置的超时时间过长,在当大批量的SQL同时执行时,每个SQL都会建立一个CancelTask对象,虽然很快执行完,且会调用CancelTask.cancel()方法,可是CancelTask方法的源代码仅仅是将本身的状态修改成:CANCELLED,而并不会直接从队列中移除这个对象, 致使OOM。 建议在cancel()后须要purge一下。
解: MySQL Connector Java 5.1.44 版本 的 StatementImpl 已经有了purge; 且 在1.8版本的JDK对Timer的cancel()也增长了移除queue内容
Timer
public int purge() { int result = 0; synchronized(queue) { for (int i = queue.size(); i > 0; i--) { if (queue.get(i).state == TimerTask.CANCELLED) { queue.quickRemove(i); result++; } } if (result != 0) queue.heapify(); } return result; } } public void cancel() { synchronized(queue) { thread.newTasksMayBeScheduled = false; queue.clear(); queue.notify(); // In case queue was already empty. } }
经过网上blog查找发现有类似的问题存在:
在issues中搜索druid的testWhileIdle问题: https://github.com/alibaba/druid/issues/1260