本文概览:介绍了超时有关的概念:@Transaction的timeout、mybatis的timeout、mysql的innodb_lock_wait_timeout。

1 问题

1.1 背景

在一个事务中完成解析一个大文件,分批存入到数据库。遇到问题,执行时间比较长,就讨论了事务超时的问题,担心执行时间太长,事务超时自动回滚了。

为了考虑这个问题,需要考虑如下超时相关的设置:

  • 一个事务的超时时间。spring的@Transactional
  • 一个stametn的执行时间。包括mybais的timeout、jdbc的timeout和mysql中的innodb_lock_wait_timeout
  • 一个connection的持续时间。myql中的wait_timeout

1.2 问题的结果

我们目的时时间执行时间过长不会被中止。所以:

  • 就不需要设置@Transactional了,因为@Transaction设置的目的是为了在一个事务执行时间超过某一个预知时抛出异常。
  • 对于mybatis的timeout也不需要设置,因为这个设置也是为了在某一个statemnet时间太久时被中止。
  • 对于myql的innodb_lock_wait_timeout,这个是针对两个事务同时更新同一行记录时,等待的最长时间,因为我们都是新增数据,所以也不需要考虑。
  • 对于connection的持续时间wait_timeout。这个时间是这个connection执行最近一个statement到当前的时间间隔,因为我们这个没解析100条记录就执行一次insert,所以两次insert的时间间隔很短,所以也不会超时。

综上,我们不需要额外的进行设置。

2 Spring的 @Transactional的超时

2.1 作用

定义一个事务的超时时间,一个事务中包含很多statement。

2.2 源码实现

1. spring实现超时

(1)根据timeout+当前时间点 赋值给一个deadLine。

(2)每一次执行sql,就会获取到一个statement时,计算liveTime =(deadline- 当前时间),分如下两种情况处理:

  • 如果liveTime>0,此时就执行stament.setQueryTimeout(liveTime);
  • 如果liveTime < 0,此时就抛出异常

(3)总结

spring实现超时通过deadLine和jdbc的stament#setQueryTime两种策略来判断超时。

2、源码分析

如果选择DataSourceTransactionManager,事务内所有的sql操作必须通过JdbcTemplate执行才能使timeout设置正常工作,通过myBatis执行的sql操作将无法应用超时设置。

(1)在开启事务时将注解中timeout赋值给connectionHolder

(2)在ResourceHolderSuport中提供了超时校验

(3)查看那些地方用到了上述接口

在DataSourceUtils中的applyTransactionTimeout和applyTiemou用到了上述接口,如下

(4)再查看用到DataSourceUtils的applyTimeout和applyTransactionTimeout的地方

  • 用到applyTimeout的地方。JdbcTemplate#applyStatementSettings。
  • 用到applyTransactionTimeout的地方,TransactionAwareDataSourceProxy#TransactionAwareInvocaitonHandler.invoke

(5)暂时没有发现使用TransactionAwareDataSourceProxy的地方,所以只需要再查看使用JdbcTemplate#applyStatementSettings地方:在JdbcTemplate的如下3个函数中使用。所以可以理解使用@Transactional的timeout的时候,必须要使用jdbcTemplate实现dao,而不能通过mybatis。

2.3 复现@Transactional的超时

由上分析,使用@Transactional的timeout属性时,需要使用JDBCtemplate实现dao,而不能使用Mybatis。JdbcTemplate配置可以参考: JDBCtemplate实例 。

1. 使用JDBC定义一个dao

2、设置事务超时的service

3、写一个测试函数

执行结果为

org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Thu Dec 08 10:22:39 CST 2016

3 Mybatis层的timeout

3.1 作用

就是设置一个stament的执行时间,包括update、delte、select和select。

3.2 源码实现

通过jdbc的statement的setQueryTimeout来实现。如下在BaseStatemntHandler中setStatementTimeout来设置超时。

3.3  复现Mybatis超时

(1)mapper.xml

设置超时时间为“1”。如下

(2)dao接口

(3)service

(4)测试类

结果为:

### SQL: UPDATE         student         SET         name = ‘timeout1’         WHERE         id = ?

### Cause: java.sql.SQLException: Query execution was interrupted

4 JDBC的timeout

介绍了JDBC的query timeout 和 socket timeout。

4.1 QueryTiemout

4.1.1  作用

就是设置stament执行时间。

4.1.2 配置

1、 默认情况下为0,即不生效。

2、 设置

通过Statement的setQueryTimeout来设置。如下

4.1.3 Statement的QueryTimeout处理过程

在执行stament时,会同时建立一个检查超时的线程,如果statment执行超时了,这个线程会向mysql的server端发送一个取消执行的请求,而不是等执行完了statment才统计时间,判断是否超时。具体流程如下:

  • 通过调用Connection的createStatement()方法创建statement
  • 调用Statement的executeQuery()方法
  • statement通过自身connection将query发送给MySQL数据库
  • statement创建一个新的timeout-execution线程用于超时处理
  • 5.1版本后改为每个connection分配一个timeout-execution线程
  • 向timeout-execution线程进行注册
  • 达到超时时间
  • TimerThread调用JtdsStatement实例中的TsdCore.cancel()方法
  • timeout-execution线程创建一个和statement配置相同的connection
  • 使用新创建的connection向超时query发送cancel query(KILL QUERY “connectionId”)

query-timeout-execution-process-for-mysql-jdbc-statement

4.2 JDBC Socket Timeout

4.2.1 作用

1、作用

在数据库被突然停掉或是发生网络错误(由于设备故障等原因)时,如果没有设置socket timeout的话,应用在数据库返回结果前会无期限地等下去,这种连接被称为dead connection。为了能够避免应用在发生网络错误时产生无休止等待的情况,所以需要设置socket timeout。

注意:socket timeout的值必须要高于statement timeout,否则,在网络正常的情况下,socket timeout将会先生效而statement timeout就失效。

2、socket timeout包含两种timeout:

(1)socket连接时的timeout:connectionTimeout

设置之后会抛出异常 java.net.SocketTimeoutException: connect timed out

(2)socket读写时的timeout:socketTimeout

设置之后会抛出异常 :java.net.SocketTimeoutException: Read timed out

4.2.2 配置

1、默认情况

connectTimeout和socketTimeout都是0,为0时表示不生效。

2、必须需要同时设置connectTimeout和sockTimeout,如下两种方式

(1)通过URL方式设置

(2)spring在xml时设置dataSource时,如下在配置duridDataSource的connectionTimeout和socketTimeout属性,可以理解为druidDataSource通过这两个属性来拼接成上述URL中的connectionTimeout和socketTimeout。

4.2.3 JDBC的conetionTimeout和socketTimeout的底层实现

1、socket连接时的timeout:connectionTimeout。

通过Socket.connect(SocketAddress endpoint, int timeout)设置

2、socket读写时的timeout:socketTimeout

通过Socket.setSoTimeout(int timeout)设置。

注意:如果不设置此属性,那么如果server端突然中断,那么客户端的就会一直阻塞在in.read上面。

4.2.4 遇到过的一个问题

1、 问题描述

之前在做一个项目时,程序运行在进行查询数据库时经常hang住了。

2、分析

在hang住的时候通过jstack打印的堆栈信息如下:

由上面的堆栈信息,发现是卡在了socketInputStream.read上面,此时只要配置socketTimeout就可以终止hang住的程序了。即通过配置druidDataSource的connectionTimeout和socketTimeout两个属性来实现,此时就不会hang住了,而是会报如下异常:

3、总结

访问数据库时必须配置这两个属性。可以在配置连接池dataSource时,配置connectionTimeout和socketTimeout两个属性来实现。或者直接通过URL指定这两个属性

5 Mysql层的indb_lock_wait_timeout

5.1 理解

1. 作用

它只针对 行锁(row lock),不同事务同时更新同一行的时候,等待其他事务提交的时间,所以一个事务中如果新增数据,那么不会受到事务超时影响。

2.变量说明

(1)变量说明

  • innodb_lock_wait_timeout(超时时间),事务等待行锁的时间。默认是50s.

(2)查看上面的两个变量

如下结果

snip20161211_36

(3)设置超时变量

3. 一个相关的变量

innodb_rollback_on_timeout ,事务等待超时之后,进行回滚的策略。默认是off,表示只回滚到上一个statement,每执行一个sql语句(一个satement)就有一个保存点,此时是回滚到上一个保存点。如果这个值改为on,那此时事务就进行整体回滚,即执行了rollback。

5.2 复现

5.2.1 在终端复现

1. 实现复现事务超时

(1)不发生事务超时的情况

开启两个终端,分别执行事务,此时事务2可以执行commit,也可以一直等待下去不会出现事务超时,因为此时事务2都是新增数据,不会涉及到行锁(行锁只发生在更新数据或者删除数据)。

snip20161211_37

(2)发生事务超时

snip20161211_38

  • 首先我们开一个终端执行上面事务1。
  • 然后再开一个终端执行事务2,此时执行事务2的时候就会hang住(如上图),一直等待下去,直到事务超时,报错“ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction”。此时可以继续执行sql,或者执行commit,或者rollbac。如下图是一个完整的例子。

snip20161211_40

(3)比较 innodb_rollback_on_timeoue为false和ture的两种情况

上面的情况下是在 innodb_rollback_on_timeout=false的情况下进行的,那么 innodb_rollback_on_timeout=true时,则表示回滚了整个事务。此时再执行一个sql,就跟原来的事务没有关系了。

5.2.2 在代码中事务超时的复现

1、mysql设置

2、代码

(1)Dao接口

(2)事务函数

(3)测试

(4)查看连接线程show processlit

snip20161211_41

(5)等待10s之后,抛出异常如

org.springframework.dao.CannotAcquireLockException:

### Error updating database.  Cause: java.sql.SQLException: Lock wait timeout exceeded; try restarting transaction

### The error may involve dao.datasource1.StudentDao.updateById-Inline

### The error occurred while setting parameters

### SQL: UPDATE         student         SET         name = ‘success24’         WHERE         id = ?

### Cause: java.sql.SQLException: Lock wait timeout exceeded; try restarting transaction

5.3  mysql 其他时间变量的解释

1. wait_timeout

参考:http://blog.csdn.net/cenfei78325747/article/details/7854611

就是一connection的sleep多长时间,通过show processlist来查看一个连接的sleep时间,这个时间是这个connection执行最近一个statement到当前的时间间隔。这个时间就是设置开一个客户端,不做任何操作的时候的连接保持时间。

2. 慢查询查询时间

snip20161211_42

6 各种TimeOut之间关系

上面结果timeout的关系,可以通过中一个http://www.importnew.com/2466.html图来表示。

(1)Spring、Mybatis和JDBC级别的超时并没有更改mysql的属性。可以查看三者的实现:

  • 对于Spring的@transactional是通过timeout属性初始化了一个deadline,每一次创建statment判断deadline是否小于0,如果小于0抛异常;否则通过JDBC的statement#setQueryTimeout来设置超时
  • Mybatis的timeout也是通过通过JDBC的statement#setQueryTimeout来设置超时。
  • JDBC的timeout,是在stament执行时,开启了一个监听线程,发现超时,就终端当前执行的stament,然后抛异常。

(2)只有mysql层没有超时的情况下。上层的JDBC或者spring层的timeout才有意义。

snip20161211_43

7 参考文献

【1】JDBC的timeout http://www.importnew.com/2466.html

【2】@Tranasaction 的timeout生效 http://www.cnblogs.com/iceJava/p/4500794.html

【3】java SocketTimout  https://my.oschina.net/shipley/blog/715196

(全文完)

分类&标签

发表评论

电子邮件地址不会被公开。 必填项已用*标注