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

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

  1. update失败,原因是数据库死锁
  2. select等待,原因是connection连接池被用光了,需要等待

get:

  1. 要勇于探索,坚持就是胜利。刚看到错误的时候直接懵逼,因为错误完全看不出来,属于框架内部报错,在犹豫是不是直接睡觉得了,毕竟也快12点了。最后还是给我一点点找到问题所在了。
  2. 同上,要敢于去深入你不了解的代码,敢于研究不懂的代码。
  3. 距离一个合格的码农越来越远了,因为越学越觉得漏洞百出,自己的代码到处都是坑。所以,一定要记录下来。

下面记录这两个问题。

1.mysql数据库死锁

这里,感谢http://www.cnblogs.com/lin-xuan/p/5280614.html,我找到了答案。在这里,我还是重现一下:

数据库死锁是事务性数据库 (如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测试代码中,因为上一个测试没有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个就会等待,不然就报错。

时间: 2024-10-25 22:03:57

Mybatis-update - 数据库死锁 - 获取数据库连接池等待的相关文章

C3P0属性设置和数据库连接池的获取

在C3p0构建时,有驱动相关信息及数据库连接池相关的属性设置,及连接的获取,今天我们先来看一下,驱动相关信息及数据库连接池相关的属性设置,在连接的获取. 从下面几句开始, Java代码   cpDSource = new ComboPooledDataSource(); //设置DriverManagerDataSource驱动相关信息 cpDSource.setDriverClass(props.getProperty("driver")); cpDSource.setJdbcUrl

基于lucene的案例开发:数据库连接池

转载请注明出处:http://blog.csdn.net/xiaojimanman/article/details/43272993 通过java程序去连接数据库时,使用的协议是TCP/IP协议,TCP/IP协议需要进行3次握手.如果每一次数据库操作都需要创建一个新的连接,都要进行3次握手,这是十分浪费资源的,程序的效率也不是很高.为了解决这个问题,我们想可不可以自己维护一些数据库连接,需要数据库操作的时候,直接使用这其中的一个连接,用完了,在还给它,这样的话就不需要每次数据库操作都创建一个新的

Tomcat6.0数据库连接池配置

http://blog.163.com/magicc_love/blog/static/185853662201111101130969/ oracle驱动包Tomcat 6.0配置oracle数据库连接池 安装Tomcat后,在我的电脑-属性—>高级-->环境变量系统变量中添加以下环境变量(假定你的tomcat安装在c:\tomcat); CATALINA_HOME: c:\tomcat CATALINA_BASE: c:\tomcat TOMCAT_HOME: c:\tomcat 然后修改

c++ 实现数据库连接池

c++ 实现数据库连接池 自己尝试用c++ 新标准实现了数据库连接池,代码简化了很多. 思路: 将数据库的连接当作一个对象添加进list队列中,在连接池创建的时候就建立好队列,并添加自定义大小的连接对象,连接对象用智能指针来管理(现代c++中不应该出现delete语句),避免类似内存泄漏等内存问题,智能指针上用lambda表达式注册了delete删除函数来释放连接资源,及时归还,(其中用了std::move来转移list中的对象所有权到函数里的临时智能指针对象,当离开作用域时,自动释放.) 关于

JDBC_数据库连接池

1.Druid:数据库连接池实现技术,由阿里巴巴提供的 步骤: 1.导入jar包 druid-1.0.9.jar   下载jar包链接: 链接:https://pan.baidu.com/s/1LnMG8U8ux3PJqDPQKo-gsg&shfl=sharepset 提取码:ur0s   2.导入配置文件,定义配置文件(该配置文件以properject形式的,可以放在任意地方) 3.加载配置文件.properject 4.获取数据库连接池对象:通过工厂来获取 DruidDataSourceFa

jsp采用数据库连接池的方法获取数据库时间戳context.xml配置,jsp页面把时间格式化成自己需要的格式

<?xml version="1.0" encoding="UTF-8"?> <!-- 数据库连接池配置文件 --> <Context> <Resource name="jdbc/ConnectionPool" auth="Application" type="javax.sql.DataSource" username="root" passw

Mybatis深入之数据库连接池原理

Mybatis深入之数据库连接池原理 简介 主要记录Mybatis数据库连接池实现原理.如何使用连接池来管理数据库连接的.连接池如何向外提供数据库连接.当外部调用使用完成之后是如何将数据库连接放回数据库连接池的. 准备 有前面的相关文章的铺垫.这里就不再从Mybatis数据库相关信息的初始化以及何时创建一个真正的数据库连接并且向外提供使用的.这两方面的过程可以参见Mybatis深入之DataSource实例化过程和Mybatis深入之获取数据库连接两篇文章. 了解Mybatis数据库连接池如何配

Mybatis深入之获取数据库连接

Mybatis深入之获取数据库连接 简介 主要记录Mybatis何时获取数据库连接以及获取数据库连接的过程.难点在于明白在使用Mybatis数据库连接池情况下的数据库连接的获取过程. 何时获取数据库连接 Mybatis只有在真正执行sql操作的时候才会去获取数据库连接.至于如何验证: 不深入源码 简单来讲就是有意将数据库配置信息写成.在一个sql执行过程中看哪一步抛数据库连接异常. public static void main(String[] args) throws Exception {

c3p0数据库连接池死锁问题

项目进行压力测试的时候,运行大概1小时候,后台抛出以下异常: Java代码   Nov 9, 2012 1:41:59 AM com.mchange.v2.async.ThreadPoolAsynchronousRunner$DeadlockDetector run WARNING: com.mchange.v2.async.ThreadPoolAsynchronousRunner$DeadlockDetector@4b9cafa7 -- APPARENT DEADLOCK!!! Complet