Mybatis的连接池

先总结一个原则:
mytatis的连接池最大值poolMaximumActiveConnections尽量跟服务器的并发访问量持平以至于大于并发访问量。

 

原因:在org.apache.ibatis.datasource.pooled.PooledDataSource中,popConnection函数(获取连接)会锁住一个PoolState对象,pushConnection函数(把连接回收到池中,在关闭连接的时候,会调用PooledConnection的invoke函数<使用的代理模式,invoke是一个回调函数>,触发close函数时调用)也会锁住这个对象。

在popConnection的时候:

1.如果池中有idle的,返回之

2.如果没有,并且池中的active连接数小于配置的最大连接数,新建一个连接返回

3.如果没有idle并且连接数已经创建到最大,就不创建新连接。从acitve connection列表中返回一个最老的值state.activeConnections.get(0),看这个连接被取出的时间(check out时间,表示从连接开始使用到目前还未close)是不是超过poolMaximumCheckoutTime(配置项,默认是20秒),如果超过,使这个连接失效,并且使用这个连接返回做下一个操作

4.如果这个连接check out时间还未到poolMaximumCheckoutTime,调用state对象的wait函数:state.wait(poolTimeToWait);等待被唤醒(在连接close的时候会调用pushConnection函数,这里会调用state对象的notifyAll,唤醒之后重新进入循环取连接)

源代码比较长就不贴了,有兴趣的同学自己下下来看!

在并发数比连接池的数量大很多的情况下,会导致大量的排除竞争来同步state对象,开销比较大!会直接导致延时大大增加。

http://www.xuebuyuan.com/1676551.html

最近学习测试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,带锁的客户端A可以正常删除,客户端B就报错:

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

http://www.cnblogs.com/woshimrf/p/5693673.html

SET [GLOBAL | SESSION] TRANSACTION
    transaction_characteristic [, transaction_characteristic] ...

transaction_characteristic:
    ISOLATION LEVEL level
  | READ WRITE
  | READ ONLY

level:
     REPEATABLE READ
   | READ COMMITTED
   | READ UNCOMMITTED
   | SERIALIZABLE

附带一些mysql相关事务级别的相关信息:

In addition, SET TRANSACTION can include an optional GLOBAL or SESSION keyword to indicate the scope of the statement.

Scope of Transaction Characteristics

You can set transaction characteristics globally, for the current session, or for the next transaction:

  • With the GLOBAL keyword, the statement applies globally for all subsequent sessions. Existing sessions are unaffected.

  • With the SESSION keyword, the statement applies to all subsequent transactions performed within the current session.

  • Without any SESSION or GLOBAL keyword, the statement applies to the next (not started) transaction performed within the current session. Subsequent transactions revert to using the SESSION isolation level.

A global change to transaction characteristics requires the SUPER privilege. Any session is free to change its session characteristics (even in the middle of a transaction), or the characteristics for its next transaction.

SET TRANSACTION without GLOBAL or SESSION is not permitted while there is an active transaction:

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.02 sec)

mysql> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
ERROR 1568 (25001): Transaction characteristics can't be changed
while a transaction is in progress

To set the global default isolation level at server startup, use the --transaction-isolation=level option to mysqld on the command line or in an option file. Values of level for this option use dashes rather than spaces, so the permissible values are READ-UNCOMMITTEDREAD-COMMITTED,REPEATABLE-READ, or SERIALIZABLE. For example, to set the default isolation level to REPEATABLE READ, use these lines in the [mysqld] section of an option file:

[mysqld]
transaction-isolation = REPEATABLE-READ

It is possible to check or set the global and session transaction isolation levels at runtime by using the tx_isolation system variable:

SELECT @@GLOBAL.tx_isolation, @@tx_isolation;
SET GLOBAL tx_isolation='REPEATABLE-READ';
SET SESSION tx_isolation='SERIALIZABLE';

Similarly, to set the transaction access mode at server startup or at runtime, use the --transaction-read-only option or tx_read_only system variable. By default, these are OFF (the mode is read/write) but can be set to ON for a default mode of read only.

Setting the global or session value of tx_isolation or tx_read_only is equivalent to setting the isolation level or access mode with SET GLOBAL TRANSACTION or SET SESSION TRANSACTION.

https://dev.mysql.com/doc/refman/5.7/en/set-transaction.html

MyISAM不支持

START TRANSACTION | BEGIN [WORK]
COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
SET AUTOCOMMIT = {0 | 1}
START TRANSACTION或BEGIN语句可以开始一项新的事务。
COMMIT可以提交当前事务,是变更成为永久变更。
ROLLBACK可以 回滚当前事务,取消其变更。
SET AUTOCOMMIT语句可以禁用或启用默认的autocommit模式,用于当前连接

自选的WORK关键词被支持,用于COMMIT和RELEASE,与CHAIN和RELEASE子句。CHAIN和RELEASE可以被用于对事务完成进行附加控制。Completion_type系统变量的值决定了默认完成的性质。

AND CHAIN子句会在当前事务结束时,立刻启动一个新事务,并且新事务与刚结束的事务有相同的隔离等级。RELEASE子句在终止了当前事务后,会让服务器断开与当前客户端的连接。包含NO关键词可以抑制CHAIN或RELEASE完成。如果completion_type系统变量被设置为一定的值,使连锁或释放完成可以默认进行,此时NO关键词有用。

默认情况下,MySQL采用autocommit模式运行。这意味着,当您执行一个用于更新(修改)表的语句之后,MySQL立刻把更新存储到磁盘中。

如果您正在使用一个事务安全型的存储引擎(如InnoDB, BDB或NDB簇),则您可以使用以下语句禁用autocommit模式:

SET AUTOCOMMIT=0;
通过把AUTOCOMMIT变量设置为禁用autocommit模式之后,您必须使用COMMIT把变更存储到磁盘中,或着如果您想要忽略从事务开始进行以来做出的变更,使用ROLLBACK。

如果您想要对于一个单一系列的语句禁用autocommit模式,则您可以使用START TRANSACTION语句:

START TRANSACTION;
SELECT @A:=SUM(salary) FROM table1 WHERE type=1;
UPDATE table2 SET summary=@A WHERE type=1;
COMMIT;
使用START TRANSACTION,autocommit仍然被禁用,直到使用COMMIT或ROLLBACK结束事务为止
然后autocommit模式恢复到原来的状态

BEGIN和BEGIN WORK被作为START TRANSACTION的别名受到支持,用于对事务进行初始化。




找来Jmeter做压力测试,结果这一测,暴露问题了,mysql返回"too many connections"这个error,这个error的具体解释参照这个链接http://dev.mysql.com/doc//refman/5.5/en/too-many-connections.html。 
     一般持久层与数据库连接都会通过一个连接池(pooled datasource)管理,方便复用连接,控制并发,比较有名的有DBCP,C3P0,BONECP等等。Mybatis3自己实现了一个连接池,在配置文件中指定datasource的type属性为POOLED即可使用。与并发关系较大的两个Mybatis连接池参数是poolMaximumActiveConnections和poolMaximumIdleConnections。 
      好了,出了问题,自然得找文档(官方手册,中英文皆有),poolMaximumActiveConnections是最大的活动连接数,活动连接,顾名思义,就是正在与数据库交互的连接,默认是10,poolMaximumIdleConnections是空闲连接数,就是没有处理请求的连接,默认是5。Mysql的max_connections我设置的是200,既最大连接数。这样一看,好像找不到问题所在,连接池最大的活动连接也就是10,跟200比还差很远,Mysql怎么会返回"too many connections"呢?在查阅文档无果后,我请教周围的一位同事,他虽然没用过Mybatis,但是他说是不是请求数超过poolMaximumActiveConnections后mybatis还会去获取连接,是不是有这样的参数控制,然后让我看看源码,说开源的东西嘛,搞不清楚就看源码。 
      我一向对源码抱有恐惧的心理,感觉那都是大神写的,我等屌丝怎能看得懂,不过被逼无奈,翻出Mybatis的源码看了一看,结果豁然开朗。找到org.apache.ibatis.datasource.pooled包下面的PooledDataSource类,这个就是连接池的实现类。可以看到里面定义了几个参数,其中就包括poolMaximumActiveConnections和poolMaximumIdleConnections,找到pushConnection方法,这个方法里会判断当前空闲连接数和poolMaximumIdleConnections的大小,如果小于他,会new PooledConnection并放进队列中,这就导致一个问题,当所有的连接被占满后,Mybatis为了保持一定的空闲连接,会不断获取新的连接,然后这些新连接被占用后,就会再去new PooledConnection,结果就是超过了mysql设置的最大连接数,然后数据库返回该错误。不知道这算不算是Mybatis的一个"坑"吧,总之在使用时要小心了,并发量大的时候就会爆掉你的数据库,解决办法很简单,将poolMaximumIdleConnections设置为0即可,果然改掉后压力测试不会爆掉数据库。 
       现在回想起来,官方文档对poolMaximumIdleConnections的定义是:在任意时间存在的空闲连接数,完全就解释了这个参数的含义,只不过当时没有仔细想,那这个参数是不是该改名字叫poolPermanentIdleConnections比较好呢,呵呵。 
问题解决了,很开心,晚上回去再仔细读下里面的源码,看看还有没有别的没发现的问题。看来源码也不是想象中的那么神秘和高深啊。其实为什么那个同事一下就能看出问题的大概,一方面是经验丰富,另一方面可能与他理解数据库连接池机制有关,归根到底,基础的东西还是最重要的。

http://my.oschina.net/sniperLi/blog/550680

 

1.如何初始化连接池
在openSession的时候,mybatis才会new第一个连接,并放入连接池。
2.mybatis是如何返回一个连接的,先上代码
2.1.首先判断是否存在空闲的连接,存在则返回一个空闲的连接(mybatis用了很多if else,感觉可读性不是很好)。
2.2.接着判断连接数是否小于最大连接数,小于则new一个新的连接返回 。
2.3 .首先得到一个非空闲时间最长的连接,判断该连接用了多久,如果超过了一个连接的最大使用时间,则返回这个连接.
2.4 上面的都不成立,就等待一个时间(默认为poolTimeToWait=20000),然后继续上面的判断。

3. mybatis是如何将一个连接放回连接池的
是通过动态代理的方式,每次调用PooledConnection的时候,就会判断方法名是否为:close,如果是就将连接放入连接池。

3.mybatis会在程序结束的时候释放连接池里面的连接吗
不会的,不过还好并发数不搞的时候创建的连接很少。
如果做高并发的测试,那就要小心数据库爆掉..

http://www.xuebuyuan.com/1487139.html

org.apache.ibatis.datasource.pooled.PooledConnection

  /*
   * Required for InvocationHandler implementation.
   *
   * @param proxy  - not used
   * @param method - the method to be executed
   * @param args   - the parameters to be passed to the method
   * @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])
   */
  public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable {
    String methodName = method.getName();
    if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
      dataSource.pushConnection(this);
      return null;
    } else {
      try {
        if (method.getDeclaringClass() != Object.class) {
          // issue #578. 
          // toString() should never fail
          // throw an SQLException instead of a Runtime
          checkConnection();
        }
        return method.invoke(realConnection, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
  }


mybatis-3-mybatis-3.2.3
org.apache.ibatis.datasource.pooled.PooledDataSource

 protected void pushConnection(PooledConnection conn) throws SQLException {

    synchronized (state) {
      state.activeConnections.remove(conn);
      if (conn.isValid()) {
        if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
          state.accumulatedCheckoutTime += conn.getCheckoutTime();
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();
          }
          PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
          state.idleConnections.add(newConn);
          newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
          newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
          conn.invalidate();
          if (log.isDebugEnabled()) {
            log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
          }
          state.notifyAll();
        } else {
          state.accumulatedCheckoutTime += conn.getCheckoutTime();
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();
          }
          conn.getRealConnection().close();
          if (log.isDebugEnabled()) {
            log.debug("Closed connection " + conn.getRealHashCode() + ".");
          }
          conn.invalidate();
        }
      } else {
        if (log.isDebugEnabled()) {
          log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
        }
        state.badConnectionCount++;
      }
    }
  }

 

jdbc.jdbcUrl=jdbc:mysql://127.0.0.1:3306/database?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true

2016-08-16 10:12:10 [http-bio-8080-exec-8:57930]  org.apache.ibatis.logging.LogFactory - [DEBUG] Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
2016-08-16 10:12:10 [http-bio-8080-exec-8:57938]  org.apache.ibatis.datasource.pooled.PooledDataSource - [DEBUG] PooledDataSource forcefully closed/removed all connections.
2016-08-16 10:12:10 [http-bio-8080-exec-8:57938]  org.apache.ibatis.datasource.pooled.PooledDataSource - [DEBUG] PooledDataSource forcefully closed/removed all connections.
2016-08-16 10:12:10 [http-bio-8080-exec-8:57938]  org.apache.ibatis.datasource.pooled.PooledDataSource - [DEBUG] PooledDataSource forcefully closed/removed all connections.
2016-08-16 10:12:10 [http-bio-8080-exec-8:57938]  org.apache.ibatis.datasource.pooled.PooledDataSource - [DEBUG] PooledDataSource forcefully closed/removed all connections.
2016-08-16 10:12:10 [http-bio-8080-exec-8:58771]  org.springframework.beans.factory.support.DefaultListableBeanFactory - [DEBUG] Eagerly caching bean 'sqlSessionFactory' to allow for resolving potential circular references
2016-08-16 10:12:10 [http-bio-8080-exec-8:58777]  org.springframework.beans.factory.support.DefaultListableBeanFactory - [DEBUG] Finished creating instance of bean 'sqlSessionFactory'
2016-08-16 10:12:10 [http-bio-8080-exec-8:58833]  org.apache.ibatis.transaction.jdbc.JdbcTransaction - [DEBUG] Opening JDBC Connection
2016-08-16 10:12:11 [http-bio-8080-exec-8:58973]  org.apache.ibatis.datasource.pooled.PooledDataSource - [DEBUG] Created connection 863970198.
2016-08-16 10:12:11 [http-bio-8080-exec-8:58974]  org.apache.ibatis.transaction.jdbc.JdbcTransaction - [DEBUG] Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@337f2396]
2016-08-16 10:12:11 [http-bio-8080-exec-8:58975]  com.selectMediaList - [DEBUG] ooo Using Connection [com.mysql.jdbc.JDBC4Connection@337f2396]
2016-08-16 10:12:11 [http-bio-8080-exec-8:58975]  com.selectMediaList - [DEBUG] ==>  Preparing: set names utf8mb4; select now();

http://rhodian.iteye.com/blog/1930891




 

posted @ 2016-08-14 10:24  沧海一滴  阅读(17203)  评论(2编辑  收藏  举报