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

相關文章
相關標籤/搜索