Mybatis的链接池

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

 

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

在popConnection的时候:mysql

1.若是池中有idle的,返回之spring

2.若是没有,而且池中的active链接数小于配置的最大链接数,新建一个链接返回sql

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,唤醒以后从新进入循环取链接)apache

源代码比较长就不贴了,有兴趣的同窗本身下下来看!安全

在并发数比链接池的数量大不少的状况下,会致使大量的排除竞争来同步state对象,开销比较大!会直接致使延时大大增长。服务器

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

最近学习测试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> 
Query OK, 0 rows affected (0.02 sec)

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

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

相关文章
相关标签/搜索