本文概览
:介绍了超时有关的概念:@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);
// 设置超时属性
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>
<
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.");
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();
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)
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
(全文完)