欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Mybatis update数据库死锁之获取数据库连接池等待

程序员文章站 2024-03-13 14:55:15
 最近学习测试mybatis,单个增删改查都没问题,最后使用mvn test的时候发现了几个问题: 1.update失败,原因是数据库死锁 2.sel...

 最近学习测试mybatis,单个增删改查都没问题,最后使用mvn test的时候发现了几个问题:

1.update失败,原因是数据库死锁

2.select等待,原因是connection连接池被用光了,需要等待

get:

1.要勇于探索,坚持就是胜利。刚看到错误的时候直接懵逼,因为错误完全看不出来,属于框架内部报错,在犹豫是不是直接睡

觉得了,毕竟也快12点了。最后还是给我一点点找到问题所在了。

2.同上,要敢于去深入你不了解的代码,敢于研究不懂的代码。

3.距离一个合格的码农越来越远了,因为越学越觉得漏洞百出,自己的代码到处都是坑。所以,一定要记录下来。

下面记录这两个问题。

1.mysql数据库死锁

这里,感谢,我找到了答案。在这里,我还是重现一下:

数据库死锁是事务性数据库 (如sql server, mysql等)经常遇到的问题。除非数据库死锁问题频繁出现导致用户无法操作,一般情况下数据库死锁问题不严重。在应用程序中进行try-catch就可以。那么数据死锁是如何产生的呢?

innodb实现的是行锁 (row level lock),分为共享锁 (s) 和 互斥锁 (x)。

•共享锁用于事务read一行。
•互斥锁用于事务update或delete一行。

当客户a持有共享锁s,并请求互斥锁x;同时客户b持有互斥锁x,并请求共享锁s。以上情况,会发生数据库死锁。如果还不够清楚,请看下面的例子。

双开两个mysql客户端

客户端a:

开启事务,并锁定共享锁s 在id=12的时候:

mysql> start transaction;
query ok, 0 rows affected (0.00 sec)
mysql> select * from blog where id = 12 lock in share mode;
+----+-------+-----------+
| id | name | author_id |
+----+-------+-----------+
| 12 | testa | 50 |
+----+-------+-----------+
1 row in set (0.00 sec)

客户端b:

开启事务,尝试删除id=12:

mysql> start transaction;
query ok, 0 rows affected (0.00 sec)
mysql> delete from blog where id = 12;

删除操作需要互斥锁 (x),但是互斥锁x和共享锁s是不能相容的。所以删除事务被放到锁请求队列中,客户b阻塞。

这时候客户端a也想要删除12:

mysql> delete from blog where id = 12;
query ok, 1 row affected (0.00 sec)

和参考文章不同的是,居然删除成功了,但客户端b出错了:

error 1213 (40001): deadlock found when trying to get lock; try restarting transaction

于是,我尝试删除13,这下都阻塞了:

Mybatis update数据库死锁之获取数据库连接池等待

我的mybatis测试代码中,因为上一个测试没有commit导致死锁,commit后就ok了。在这里,我想说,数据库的东西全还给老师了,关于锁以及事务需要重新温习一下了。

2.mybatis中datasource的数据库连接数

当我mvn test的时候,我发现有个查询的test打印日志:

2016-07-21 23:43:53,356 debug [org.apache.ibatis.transaction.jdbc.jdbctransaction] - opening jdbc connection
2016-07-21 23:43:53,356 debug [org.apache.ibatis.datasource.pooled.pooleddatasource] - waiting as long as 20000 milliseconds for connection.

于是,果然等了一段时间后才执行成功。跟踪源码,找到这处日志就明白了。首先,我这里使用的数据库连接配置是mybatis默认的:

<environment id="development">
<transactionmanager type="jdbc"/>
<datasource type="pooled">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</datasource>
</environment> 
当数据库连接池的连接数用光了之后就要等2s再去获取:
while (conn == null) {
synchronized (state) {
if (!state.idleconnections.isempty()) {
// pool has available connection
conn = state.idleconnections.remove(0);
if (log.isdebugenabled()) {
log.debug("checked out connection " + conn.getrealhashcode() + " from pool.");
}
} else {
// pool does not have available connection
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) {
log.debug("bad connection. could not roll back");
} 
}
conn = new pooledconnection(oldestactiveconnection.getrealconnection(), this);
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;
}
}
}
}
if (conn != null) {
if (conn.isvalid()) {
if (!conn.getrealconnection().getautocommit()) {
conn.getrealconnection().rollback();
}
conn.setconnectiontypecode(assembleconnectiontypecode(datasource.geturl(), username, password));
conn.setcheckouttimestamp(system.currenttimemillis());
conn.setlastusedtimestamp(system.currenttimemillis());
state.activeconnections.add(conn);
state.requestcount++;
state.accumulatedrequesttime += system.currenttimemillis() - t;
} else {
if (log.isdebugenabled()) {
log.debug("a bad connection (" + conn.getrealhashcode() + ") was returned from the pool, getting another connection.");
}
state.badconnectioncount++;
localbadconnectioncount++;
conn = null;
if (localbadconnectioncount > (poolmaximumidleconnections + 3)) {
if (log.isdebugenabled()) {
log.debug("pooleddatasource: could not get a good connection to the database.");
}
throw new sqlexception("pooleddatasource: could not get a good connection to the database.");
}
}
}
}
}

当连接数少于10个的时候回创建,超过10个就会等待,不然就报错。

以上所述是小编给大家介绍的mybatis update数据库死锁之获取数据库连接池等待,希望对大家有所帮助