添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

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

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

    public long getTimeToLiveInMillis() throws TransactionTimedOutException{ if (this.deadline == null) { throw new IllegalStateException("No timeout specified for this resource holder"); long timeToLive = this.deadline.getTime() - System.currentTimeMillis(); // 校验timeout checkTransactionTimeout(timeToLive <= 0); return timeToLive; // 如果超时抛出异常 private void checkTransactionTimeout(boolean deadlineReached) throws TransactionTimedOutException { if (deadlineReached) { setRollbackOnly(); throw new TransactionTimedOutException("Transaction timed out: deadline was " + this.deadline);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public long getTimeToLiveInMillis ( ) throws TransactionTimedOutException {
    if ( this . deadline == null ) {
    throw new IllegalStateException ( "No timeout specified for this resource holder" ) ;
    }
    long timeToLive = this . deadline . getTime ( ) - System . currentTimeMillis ( ) ;
    // 校验timeout
    checkTransactionTimeout ( timeToLive <= 0 ) ;
    return timeToLive ;
    }
    // 如果超时抛出异常
    private void checkTransactionTimeout ( boolean deadlineReached ) throws TransactionTimedOutException {
    if ( deadlineReached ) {
    setRollbackOnly ( ) ;
    throw new TransactionTimedOutException ( "Transaction timed out: deadline was " + this . deadline ) ;
    }
    }
    public static void applyTransactionTimeout(Statement stmt, DataSource dataSource) throws SQLException { applyTimeout(stmt, dataSource, 0); public static void applyTimeout(Statement stmt, DataSource dataSource, int timeout) throws SQLException { Assert.notNull(stmt, "No Statement specified"); Assert.notNull(dataSource, "No DataSource specified"); ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); if (holder != null && holder.hasTimeout()) { // 设置statment的超时时间 stmt.setQueryTimeout(holder.getTimeToLiveInSeconds()); else if (timeout > 0) { // No current transaction timeout -> apply specified value. stmt.setQueryTimeout(timeout);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public static void applyTransactionTimeout ( Statement stmt , DataSource dataSource ) throws SQLException {
    applyTimeout ( stmt , dataSource , 0 ) ;
    }
    public static void applyTimeout ( Statement stmt , DataSource dataSource , int timeout ) throws SQLException {
    Assert . notNull ( stmt , "No Statement specified" ) ;
    Assert . notNull ( dataSource , "No DataSource specified" ) ;
    ConnectionHolder holder = ( ConnectionHolder ) TransactionSynchronizationManager . getResource ( dataSource ) ;
    if ( holder != null && holder . hasTimeout ( ) ) {
    // 设置statment的超时时间
    stmt . setQueryTimeout ( holder . getTimeToLiveInSeconds ( ) ) ;
    }
    else if ( timeout > 0 ) {
    // No current transaction timeout -> apply specified value.
    stmt . setQueryTimeout ( timeout ) ;
    }
    }

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

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

    public <T> T execute(CallableStatementCreator csc, CallableStatementCallback<T> action){ public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action){ public <T> T execute(StatementCallback<T> action) throws DataAccessException {

    2.3 复现@Transactional的超时

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

    1. 使用JDBC定义一个dao

    private JdbcTemplate jdbcTemplate; public void update() { jdbcTemplate.execute(new StatementCallback<Integer>() { public Integer doInStatement(Statement stmt) throws SQLException, DataAccessException { stmt.execute("update student set name='success511' where id = 1;"); return 1;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @ Service
    public class StudentDaoWithJdbcTemplate {
    @ Resource
    private JdbcTemplate jdbcTemplate ;
    public void update ( ) {
    jdbcTemplate . execute ( new StatementCallback < Integer > ( ) {
    public Integer doInStatement ( Statement stmt ) throws SQLException , DataAccessException {
    stmt . execute ( "update student set name='success511' where id = 1;" ) ;
    return 1 ;
    }
    } ) ;
    }
    }
    @Service public class TimeOutService { private static final Logger logger = LoggerFactory.getLogger(TimeOutService.class); @Resource private StudentDaoWithJdbcTemplate studentDaoWithJdbcTemplate; * 测试注解@Transactional的超时timeout---jdbcTemlate实现dao @Transactional(value = "transactionManager", timeout = 1) public void testAnnotationTransactionalTimeOutWithJdbcTemplate() { try { Thread.sleep(3000); logger.info("sleep 3s end"); } catch (Exception e) { studentDaoWithJdbcTemplate.update();
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @ Service
    public class TimeOutService {
    private static final Logger logger = LoggerFactory . getLogger ( TimeOutService . class ) ;
    @ Resource
    private StudentDaoWithJdbcTemplate studentDaoWithJdbcTemplate ;
    /**
    * 测试注解@Transactional的超时timeout---jdbcTemlate实现dao
    */
    @ Transactional ( value = "transactionManager" , timeout = 1 )
    public void testAnnotationTransactionalTimeOutWithJdbcTemplate ( ) {
    try {
    Thread . sleep ( 3000 ) ;
    logger . info ( "sleep 3s end" ) ;
    } catch ( Exception e ) {
    }
    studentDaoWithJdbcTemplate . update ( ) ;
    }
    }
    @Test public void testAnnotationTransactionalServiceTestWithJdbcTemplate(){ timeOutService.testAnnotationTransactionalTimeOutWithJdbcTemplate();

    执行结果为

    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来设置超时。

    // 设置超时属性 protected void setStatementTimeout(Statement stmt) throws SQLException { Integer timeout = mappedStatement.getTimeout(); Integer defaultTimeout = configuration.getDefaultStatementTimeout(); if (timeout != null) { stmt.setQueryTimeout(timeout); } else if (defaultTimeout != null) { stmt.setQueryTimeout(defaultTimeout);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 设置超时属性
    protected void setStatementTimeout ( Statement stmt ) throws SQLException {
    Integer timeout = mappedStatement . getTimeout ( ) ;
    Integer defaultTimeout = configuration . getDefaultStatementTimeout ( ) ;
    if ( timeout != null ) {
    stmt . setQueryTimeout ( timeout ) ;
    } else if ( defaultTimeout != null ) {
    stmt . setQueryTimeout ( defaultTimeout ) ;
    }
    }
    @Service public class TimeOutService { private static final Logger logger = LoggerFactory.getLogger(TimeOutService.class); @Resource private StudentDao studentDao; * 测试mybatis配置的超时,这里还需要开启事务,通过利用不同事务中同时更新同一条语句时,需要等待行锁,来实现增长sql的执行时间 @Transactional(value = "transactionManager") public void testMybatisTimeOut() { studentDao.updateCheckTimeOut(1); try { Thread.sleep(3000); logger.info("sleep 3s end"); } catch (Exception e) {
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @ Service
    public class TimeOutService {
    private static final Logger logger = LoggerFactory . getLogger ( TimeOutService . class ) ;
    @ Resource
    private StudentDao studentDao ;
    /**
    * 测试mybatis配置的超时,这里还需要开启事务,通过利用不同事务中同时更新同一条语句时,需要等待行锁,来实现增长sql的执行时间
    */
    @ Transactional ( value = "transactionManager" )
    public void testMybatisTimeOut ( ) {
    studentDao . updateCheckTimeOut ( 1 ) ;
    try {
    Thread . sleep ( 3000 ) ;
    logger . info ( "sleep 3s end" ) ;
    } catch ( Exception e ) {
    }
    }
    }
    public void testMybatisTimeOut(){ Thread t1 = new Thread(new Runnable() { public void run() { timeOutService.testMybatisTimeOut(); Thread t2 = new Thread(new Runnable() { public void run() { timeOutService.testMybatisTimeOut(); t1.start(); t2.start(); // 为了查看子线程执行情况,所以这里使用while-true while(true){
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /**
    * 测试mybais的timeout
    */
    @ Test
    public void testMybatisTimeOut ( ) {
    Thread t1 = new Thread ( new Runnable ( ) {
    public void run ( ) {
    timeOutService . testMybatisTimeOut ( ) ;
    }
    } ) ;
    Thread t2 = new Thread ( new Runnable ( ) {
    public void run ( ) {
    timeOutService . testMybatisTimeOut ( ) ;
    }
    } ) ;
    t1 . start ( ) ;
    t2 . start ( ) ;
    // 为了查看子线程执行情况,所以这里使用while-true
    while ( true ) {
    }
    }

    ### 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”)
  • 4.2 JDBC Socket Timeout

    4.2.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方式设置

    <bean id="transDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> .......... <property name="connectProperties"> <props> <prop key="connectTimeout">1000</prop> <prop key="socketTimeout">1000</prop> </props> </property> .............. </bean>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    < bean id = "transDataSource" class = "com.alibaba.druid.pool.DruidDataSource" init - method = "init"
    destroy - method = "close" >
    . . . . . . . . . .
    < property name = "connectProperties" >
    < props >
    < prop key = "connectTimeout" > 1000 < / prop >
    < prop key = "socketTimeout" > 1000 < / prop >
    < / props >
    < / property >
    . . . . . . . . . . . . . .
    < / bean >
    t1 = System.currentTimeMillis(); // 设置connect timeout 为2000毫秒 socket.connect(new InetSocketAddress("www.ss.ssss", 8080), 2000); } catch (IOException e) { long t2 = System.currentTimeMillis(); e.printStackTrace(); System.out.println("Connect failed, take time -> " + (t2 - t1) + "ms.");
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    socket = new Socket ( ) ;
    long t1 = 0 ;
    try {
    t1 = System . currentTimeMillis ( ) ;
    // 设置connect timeout 为2000毫秒
    socket . connect ( new InetSocketAddress ( "www.ss.ssss" , 8080 ) , 2000 ) ;
    } catch ( IOException e ) {
    long t2 = System . currentTimeMillis ( ) ;
    e . printStackTrace ( ) ;
    System . out . println ( "Connect failed, take time -> " + ( t2 - t1 ) + "ms." ) ;
    }
    socket = new Socket(); socket.connect(new InetSocketAddress("localhost", 8080)); // 设置so timeout 为2000毫秒 socket.setSoTimeout(2000); System.out.println("Connected."); in = socket.getInputStream(); System.out.println("reading..."); t1 = System.currentTimeMillis(); in.read();
    1
    2
    3
    4
    5
    6
    7
    8
    9
    socket = new Socket ( ) ;
    socket . connect ( new InetSocketAddress ( "localhost" , 8080 ) ) ;
    // 设置so timeout 为2000毫秒
    socket . setSoTimeout ( 2000 ) ;
    System . out . println ( "Connected." ) ;
    in = socket . getInputStream ( ) ;
    System . out . println ( "reading..." ) ;
    t1 = System . currentTimeMillis ( ) ;
    in . read ( ) ;
    "main" prio=10 tid=0x00007ff27400a000 nid=0x7d88 runnable [0x00007ff2792d5000] java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.read(SocketInputStream.java:152) at java.net.SocketInputStream.read(SocketInputStream.java:122) at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:113) at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:160) at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:188) - locked <0x00000006bca6d6c0> (a com.mysql.jdbc.util.ReadAheadInputStream) at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:2331) at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2776) at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2765) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3301) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1853) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1976) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2497) - locked <0x00000006bca69b00> (a java.lang.Object) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2426) at com.mysql.jdbc.StatementImpl.executeQuery(StatementImpl.java:1383) - locked <0x00000006bca69b00> (a java.lang.Object) at com.mysql.jdbc.ConnectionImpl.getTransactionIsolation(ConnectionImpl.java:3074) at com.alibaba.druid.filter.FilterChainImpl.connection_getTransactionIsolation(FilterChainImpl.java:347) at com.alibaba.druid.filter.FilterAdapter.connection_getTransactionIsolation(FilterAdapter.java:871) at com.alibaba.druid.filter.FilterChainImpl.connection_getTransactionIsolation(FilterChainImpl.java:344) at com.alibaba.druid.filter.FilterAdapter.connection_getTransactionIsolation(FilterAdapter.java:871) at com.alibaba.druid.filter.FilterChainImpl.connection_getTransactionIsolation(FilterChainImpl.java:344) at com.alibaba.druid.filter.FilterAdapter.connection_getTransactionIsolation(FilterAdapter.java:871) at com.alibaba.druid.filter.FilterChainImpl.connection_getTransactionIsolation(FilterChainImpl.java:344) at com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl.getTransactionIsolation(ConnectionProxyImpl.java:260)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    "main" prio = 10 tid = 0x00007ff27400a000 nid = 0x7d88 runnable [ 0x00007ff2792d5000 ]
    java . lang . Thread . State : RUNNABLE
    at java . net . SocketInputStream . socketRead0 ( Native Method )
    at java . net . SocketInputStream . read ( SocketInputStream . java : 152 )
    at java . net . SocketInputStream . read ( SocketInputStream . java : 122 )
    at com . mysql . jdbc . util . ReadAheadInputStream . fill ( ReadAheadInputStream . java : 113 )
    at com . mysql . jdbc . util . ReadAheadInputStream . readFromUnderlyingStreamIfNecessary ( ReadAheadInputStream . java : 160 )
    at com . mysql . jdbc . util . ReadAheadInputStream . read ( ReadAheadInputStream . java : 188 )
    - locked < 0x00000006bca6d6c0 > ( a com . mysql . jdbc . util . ReadAheadInputStream )
    at com . mysql . jdbc . MysqlIO . readFully ( MysqlIO . java : 2331 )
    at com . mysql . jdbc . MysqlIO . reuseAndReadPacket ( MysqlIO . java : 2776 )
    at com . mysql . jdbc . MysqlIO . reuseAndReadPacket ( MysqlIO . java : 2765 )
    at com . mysql . jdbc . MysqlIO . checkErrorPacket ( MysqlIO . java : 3301 )
    at com . mysql . jdbc . MysqlIO . sendCommand ( MysqlIO . java : 1853 )
    at com . mysql . jdbc . MysqlIO . sqlQueryDirect ( MysqlIO . java : 1976 )
    at com . mysql . jdbc . ConnectionImpl . execSQL ( ConnectionImpl . java : 2497 )
    - locked < 0x00000006bca69b00 > ( a java . lang . Object )
    at com . mysql . jdbc . ConnectionImpl . execSQL ( ConnectionImpl . java : 2426 )
    at com . mysql . jdbc . StatementImpl . executeQuery ( StatementImpl . java : 1383 )
    - locked < 0x00000006bca69b00 > ( a java . lang . Object )
    at com . mysql . jdbc . ConnectionImpl . getTransactionIsolation ( ConnectionImpl . java : 3074 )
    at com . alibaba . druid . filter . FilterChainImpl . connection_getTransactionIsolation ( FilterChainImpl . java : 347 )
    at com . alibaba . druid . filter . FilterAdapter . connection_getTransactionIsolation ( FilterAdapter . java : 871 )
    at com . alibaba . druid . filter . FilterChainImpl . connection_getTransactionIsolation ( FilterChainImpl . java : 344 )
    at com . alibaba . druid . filter . FilterAdapter . connection_getTransactionIsolation ( FilterAdapter . java : 871 )
    at com . alibaba . druid . filter . FilterChainImpl . connection_getTransactionIsolation ( FilterChainImpl . java : 344 )
    at com . alibaba . druid . filter . FilterAdapter . connection_getTransactionIsolation ( FilterAdapter . java : 871 )
    at com . alibaba . druid . filter . FilterChainImpl . connection_getTransactionIsolation ( FilterChainImpl . java : 344 )
    at com . alibaba . druid . proxy . jdbc . ConnectionProxyImpl . getTransactionIsolation ( ConnectionProxyImpl . java : 260 )
    Caused by: java.net.SocketTimeoutException: Read timed out at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.read(SocketInputStream.java:152) at java.net.SocketInputStream.read(SocketInputStream.java:122) at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:100) at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:143) at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:173) at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:2911) at com.mysql.jdbc.MysqlIO.nextRowFast(MysqlIO.java:2116)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Caused by : java . net . SocketTimeoutException : Read timed out
    at java . net . SocketInputStream . socketRead0 ( Native Method )
    at java . net . SocketInputStream . read ( SocketInputStream . java : 152 )
    at java . net . SocketInputStream . read ( SocketInputStream . java : 122 )
    at com . mysql . jdbc . util . ReadAheadInputStream . fill ( ReadAheadInputStream . java : 100 )
    at com . mysql . jdbc . util . ReadAheadInputStream . readFromUnderlyingStreamIfNecessary ( ReadAheadInputStream . java : 143 )
    at com . mysql . jdbc . util . ReadAheadInputStream . read ( ReadAheadInputStream . java : 173 )
    at com . mysql . jdbc . MysqlIO . readFully ( MysqlIO . java : 2911 )
    at com . mysql . jdbc . MysqlIO . nextRowFast ( MysqlIO . java : 2116 )

    3. 一个相关的变量

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

    5.2 复现

    5.2.1 在终端复现

    1. 实现复现事务超时

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

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

    (2)发生事务超时

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

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

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

    1、mysql设置

    @Transactional(value="transactionManager") public void excutetransactionTimeOut(){ logger.info("f1 beg"); studentDao.updateById(1); while(true){} public void testTransactionTimeOut() { Thread t1 = new Thread(new Runnable() { public void run() { transactionService.executeTransactionTimeOut(); Thread t2 = new Thread(new Runnable() { public void run() { transactionService.executeTransactionTimeOut(); t1.start(); t2.start(); while (true){
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    * 测试事务超时
    */
    @ Test
    public void testTransactionTimeOut ( ) {
    Thread t1 = new Thread ( new Runnable ( ) {
    public void run ( ) {
    transactionService . executeTransactionTimeOut ( ) ;
    }
    } ) ;
    Thread t2 = new Thread ( new Runnable ( ) {
    public void run ( ) {
    transactionService . executeTransactionTimeOut ( ) ;
    }
    } ) ;
    t1 . start ( ) ;
    t2 . start ( ) ;
    while ( true ) {
    }
    }

    (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. 慢查询查询时间

    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才有意义。

    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

    (全文完)