最近在工作中遇到一个sqlSession的使用问题。老项目不知道为什么单独搞了一个配置文件使用Mybatis。
在测试中发现一个性能问题:执行单独使用Mybatis查询使。每查询一定的次数就会出现卡顿。通过跟踪发现没次卡顿还都是20s左右。
但是为什么会这样呢百思不得其姐。最后写了个dome测试也是只使用了Mybatis:
贴代码:
public class SqlSessionTest {
public static void main(String[] args) throws IOException {
//加载mybatis配置文件(也会加载关联的映射文件)
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//构建sqlSession工厂
//静态代理
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
for (int i = 0; i <21 ; i++) {
long l = System.currentTimeMillis();
SqlSession sqlSession1 = sqlSessionFactory.openSession();
MyCat myCat1 = sqlSession1.selectOne("com.wly.dao.MyCatDao.getById", 1L);
System.out.println(System.currentTimeMillis()-l);
System.out.println(myCat1.getName()+i+"============================"+sqlSession1.getConnection()+"=======================");
}
}
}
输出:
305
小电驴0============================com.mysql.jdbc.JDBC4Connection@100fc185=======================
23
小电驴1============================com.mysql.jdbc.JDBC4Connection@548a9f61=======================
9
小电驴2============================com.mysql.jdbc.JDBC4Connection@20e2cbe0=======================
65
小电驴3============================com.mysql.jdbc.JDBC4Connection@482f8f11=======================
9
小电驴4============================com.mysql.jdbc.JDBC4Connection@724af044=======================
10
小电驴5============================com.mysql.jdbc.JDBC4Connection@55f3ddb1=======================
9
小电驴6============================com.mysql.jdbc.JDBC4Connection@2b98378d=======================
8
小电驴7============================com.mysql.jdbc.JDBC4Connection@14acaea5=======================
9
小电驴8============================com.mysql.jdbc.JDBC4Connection@4501b7af=======================
24
小电驴9============================com.mysql.jdbc.JDBC4Connection@3ffc5af1=======================
20001
小电驴10============================com.mysql.jdbc.JDBC4Connection@100fc185=======================
1
小电驴11============================com.mysql.jdbc.JDBC4Connection@548a9f61=======================
8
小电驴12============================com.mysql.jdbc.JDBC4Connection@20e2cbe0=======================
1
小电驴13============================com.mysql.jdbc.JDBC4Connection@482f8f11=======================
1
小电驴14============================com.mysql.jdbc.JDBC4Connection@724af044=======================
2
小电驴15============================com.mysql.jdbc.JDBC4Connection@55f3ddb1=======================
2
小电驴16============================com.mysql.jdbc.JDBC4Connection@2b98378d=======================
0
小电驴17============================com.mysql.jdbc.JDBC4Connection@14acaea5=======================
1
小电驴18============================com.mysql.jdbc.JDBC4Connection@4501b7af=======================
1
小电驴19============================com.mysql.jdbc.JDBC4Connection@3ffc5af1=======================
20002
小电驴20============================com.mysql.jdbc.JDBC4Connection@100fc185=======================
发现问题,每第十一条就会卡。
之前了解到sqlSession在执行数据库操作的时候才会去获取连接。就是JDBC的Connection。然后跟踪代码sqlSession1.selectOne("com.wly.dao.MyCatDao.getById", 1L);此代码会调用到Mybatis的PooledDataSource类的popConnection(String username, String password)方法获取数据库连接,中间的调用过程不累赘了,。这个方法比较长这里贴上获取Connection的代码
while (conn == null) {
synchronized (state) {
// 检测空闲连接集合(idleConnections)是否为空
if (!state.idleConnections.isEmpty()) {
// Pool has available connection
// idleConnections 不为空,表示有空闲连接可以使用
conn = state.idleConnections.remove(0);
if (log.isDebugEnabled()) {
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
} else {
// Pool does not have available connection
// 暂无空闲连接可用,但如果活跃连接数还未超出限制
//(poolMaximumActiveConnections),则可创建新的连接
if (state.activeConnections.size() < poolMaximumActiveConnections) {
// Can create new connection
// 创建新连接
conn = new PooledConnection(dataSource.getConnection(), this);
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else {
// 连接池已满,不能创建新连接
// 取出运行时间最长的连接
// Cannot create new connection
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
// 获取运行时长
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
// 检测运行时长是否超出限制,即超时
if (longestCheckoutTime > poolMaximumCheckoutTime) {
// Can claim overdue connection
// 累加超时相关的统计字段
state.claimedOverdueConnectionCount++;
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
state.accumulatedCheckoutTime += longestCheckoutTime;
// 从活跃连接集合中移除超时连接
state.activeConnections.remove(oldestActiveConnection);
// 若连接未设置自动提交,此处进行回滚操作
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
try {
oldestActiveConnection.getRealConnection().rollback();
} catch (SQLException e) {
/*
Just log a message for debug and continue to execute the following
statement like nothing happened.
Wrap the bad connection with a new PooledConnection, this will help
to not interrupt current executing thread and give current thread a
chance to join the next competition for another valid/good database
connection. At the end of this loop, bad {@link @conn} will be set as null.
*/
log.debug("Bad connection. Could not roll back");
}
}
// 创建一个新的 PooledConnection,注意,此处复用
// oldestActiveConnection 的 realConnection 变量
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
// 复用 oldestActiveConnection 的一些信息,注意
// PooledConnection 中的 createdTimestamp 用于记录
// Connection 的创建时间,而非 PooledConnection
// 的创建时间。所以这里要复用原连接的时间信息。
conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
// 设置连接为无效状态
oldestActiveConnection.invalidate();
if (log.isDebugEnabled()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} else {
// Must wait
// 运行时间最长的连接并未超时
try {
if (!countedWait) {
state.hadToWaitCount++;
countedWait = true;
}
if (log.isDebugEnabled()) {
log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
}
long wt = System.currentTimeMillis();
// 当前线程进入等待状态
state.wait(poolTimeToWait);
state.accumulatedWaitTime += System.currentTimeMillis() - wt;
} catch (InterruptedException e) {
break;
}
}
}
}
从连接池中获取连接首先会遇到两种情况:
1. 连接池中有空闲连接
2. 连接池中无空闲连接对于第一种情况,处理措施就很简单了,把连接取出返回即可。
对于第二种情况,则要进行细分,会有如下的情况:
1. 活跃连接数没有超出最大活跃连接数
2. 活跃连接数超出最大活跃连接数对于上面两种情况,
第一种情况比较好处理,直接创建新的连接即可。至于第二种情况,需要再次进行细分。
1. 活跃连接的运行时间超出限制,即超时了
2. 活跃连接未超时
对于第一种情况,我们直接将超时连接强行中断,并进行回滚,然后复用部分字段重新创建 PooledConnection 即可。对于第二种情况,目前没有更好的处理方式了,只能等待了。
我的代码每到第十条就会选择第 二、二、二种情况。所以就只能等待其它的连接超时了。恰好默认的超时时间是2000ms。
如果我们使用Spring来管理Mybatis的话这些操作是不会发生的,Spring会自动事务提交,并且会自动关闭sqlSession
解决方法:
①单独使用Mybatis时需要自己手动提交,sqlSession.commit(),如果只是查询的话可以不提交。但是不管什么操作最后记得需要关闭sqlSession资源,sqlSession.close();
②控制sqlSession的数量,但需要注意sqlSession是线程不安全的