連接池詳解


連接池:對於共享資源,有一個很著名的設計模式:資源池(Resource Pool)。該模式正是為了解決資源的頻繁分配﹑釋放所造成的問題。為解決我們的問題,可以采用數據庫連接池技術。數據庫連接池的基本思想就是為數據庫連接建立一個“緩沖池”。預先在緩沖池中放入一定數量的連接,當需要建立數據庫連接時,只需從“緩沖池”中取出一個,使用完畢之后再放回去。我們可以通過設定連接池最大連接數來防止系統無盡的與數據庫連接。更為重要的是我們可以通過連接池的管理機制監視數據庫的連接的數量﹑使用情況,為系統開發﹑測試及性能調整提供依據。

數據庫連接池的主要操作如下:
(1)建立數據庫連接池對象(服務器啟動)。
(2)按照事先指定的參數創建初始數量的數據庫連接(即:空閑連接數)。
(3)對於一個數據庫訪問請求,直接從連接池中得到一個連接。如果數據庫連接池對象中沒有空閑的連接,且連接數沒有達到最大(即:最大活躍連接數),創建一個新的數據庫連接。
(4)存取數據庫。
(5)關閉數據庫,釋放所有數據庫連接(此時的關閉數據庫連接,並非真正關閉,而是將其放入空閑隊列中。如實際空閑連接數大於初始空閑連接數則釋放連接)。
(6)釋放數據庫連接池對象(服務器停止、維護期間,釋放數據庫連接池對象,並釋放所有連接)。
PS.java的所有的連接池 無論是c3p0、dbcp還是druid,都有一個類似maxWait或者maxIdleTime配置項。具體含義就是當連接長時間沒有向服務器發請求的時候,斷開這個連接,避免對數據庫連接的浪費。這個時間不是隨便設的,它的依據是數據庫的連接最大空閑時間。

tomcat DHCP的配置
<Resource driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver"
logAbandoned="true" maxActive="20" maxIdle="2" maxWait="5000" name="system"
removeAbandonedTimeout="60" removeAbandoned="true"
password="xx" type="javax.sql.DataSource"
url="jdbc:sqlserver://127.0.0.1:1433;DatabaseName=base"
username="sa"/>
當中的logAbandoned="true" removeAbandoned="true" removeAbandonedTimeout="60"就是用來配置數據庫斷開后自動連接的。


數據庫連接池會在啟動時就建立所需的若干連接,並一直保持連接狀態,但是當數據庫服務停止后,這些連接就被外部因素給中斷了
網上優化了的配置信息:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${db.driverClassName}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
<!--initialSize: 初始化連接-->
<property name="initialSize" value="5"/>
<!--maxIdle: 最大空閑連接-->
<property name="maxIdle" value="10"/>
<!--minIdle: 最小空閑連接-->
<property name="minIdle" value="5"/>
<!--maxActive: 最大連接數量-->
<property name="maxActive" value="15"/>
<!--removeAbandoned: 是否自動回收超時連接-->
<property name="removeAbandoned" value="true"/>
<!--removeAbandonedTimeout: 超時時間(以秒數為單位)-->
<property name="removeAbandonedTimeout" value="180"/>
<!--maxWait: 超時等待時間以毫秒為單位 6000毫秒/1000等於60秒-->
<property name="maxWait" value="3000"/>
<property name="validationQuery">
<value>SELECT 1</value>
</property>
<property name="testOnBorrow">
<value>true</value>
</property>
</bean>

參數 描述
username 傳遞給JDBC驅動的用於建立連接的用戶名
password 傳遞給JDBC驅動的用於建立連接的密碼
url 傳遞給JDBC驅動的用於建立連接的URL
driverClassName 使用的JDBC驅動的完整有效的java 類名
connectionProperties 當建立新連接時被發送給JDBC驅動的連接參數,
格式必須是 [propertyName=property;]*
注意 :參數user/password將被明確傳遞,所以不需要包括在這里。

參數 默認值 描述
defaultAutoCommit true 連接池創建的連接的默認的auto-commit狀態
defaultReadOnly driver default 連接池創建的連接的默認的read-only狀態.
如果沒有設置則setReadOnly方法將不會被調用. (某些驅動不支持只讀模式,比如:Informix)
defaultTransactionIsolation driver default 連接池創建的連接的默認的TransactionIsolation狀態.
下面列表當中的某一個: (參考javadoc)

* NONE
* READ_COMMITTED
* READ_UNCOMMITTED
* REPEATABLE_READ
* SERIALIZABLE

defaultCatalog 連接池創建的連接的默認的catalog

參數 默認值 描述
initialSize 0 初始化連接:連接池啟動時創建的初始化連接數量,1.2版本后支持
maxActive 8 最大活動連接:連接池在同一時間能夠分配的最大活動連接的數量,
如果設置為非正數則表示不限制
maxIdle 8 最大空閑連接:連接池中容許保持空閑狀態的最大連接數量,超過的空閑連接將被釋放,
如果設置為負數表示不限制
minIdle 0 最小空閑連接:連接池中容許保持空閑狀態的最小連接數量,低於這個數量將創建新的連接,
如果設置為0則不創建
maxWait 無限 最大等待時間:當沒有可用連接時,連接池等待連接被歸還的最大時間(以毫秒計數),
超過時間則拋出異常,如果設置為-1表示無限等待

參數 默認值 描述
validationQuery SQL查詢,用來驗證從連接池取出的連接,在將連接返回給調用者之前.如果指定,
則查詢必須是一個SQL SELECT並且必須返回至少一行記錄
testOnBorrow true 指明是否在從池中取出連接前進行檢驗,如果檢驗失敗,
則從池中去除連接並嘗試取出另一個.
注意: 設置為true后如果要生效,validationQuery參數必須設置為非空字符串
testOnReturn false 指明是否在歸還到池中前進行檢驗
注意: 設置為true后如果要生效,validationQuery參數必須設置為非空字符串
testWhileIdle false 指明連接是否被空閑連接回收器(如果有)進行檢驗.如果檢測失敗,
則連接將被從池中去除.
注意: 設置為true后如果要生效,validationQuery參數必須設置為非空字符串
timeBetweenEvictionRunsMillis -1 在空閑連接回收器線程運行期間休眠的時間值,以毫秒為單位.
如果設置為非正數,則不運行空閑連接回收器線程
numTestsPerEvictionRun 3 在每次空閑連接回收器線程(如果有)運行時檢查的連接數量
minEvictableIdleTimeMillis 1000 * 60 * 30 連接在池中保持空閑而不被空閑連接回收器線程
(如果有)回收的最小時間值,單位毫秒

參數 默認值 描述
poolPreparedStatements false 開啟池的prepared statement 池功能
maxOpenPreparedStatements 不限制 statement池能夠同時分配的打開的statements的最大數量,
如果設置為0表示不限制


這里可以開啟PreparedStatements池. 當開啟時, 將為每個連接創建一個statement池,
並且被下面方法創建的PreparedStatements將被緩存起來:
* public PreparedStatement prepareStatement(String sql)
* public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
注意: 確認連接還有剩余資源可以留給其他statement
參數 默認值 描述
accessToUnderlyingConnectionAllowed false 控制PoolGuard是否容許獲取底層連接


如果容許則可以使用下面的方式來獲取底層連接:
Connection conn = ds.getConnection();
Connection dconn = ((DelegatingConnection) conn).getInnermostDelegate();
...
conn.close();

默認false不開啟, 這是一個有潛在危險的功能, 不適當的編碼會造成傷害.
(關閉底層連接或者在守護連接已經關閉的情況下繼續使用它).請謹慎使用,
並且僅當需要直接訪問驅動的特定功能時使用.
注意: 不要關閉底層連接, 只能關閉前面的那個.
參數 默認值 描述
removeAbandoned false 標記是否刪除泄露的連接,如果他們超過了removeAbandonedTimout的限制.
如果設置為true, 連接被認為是被泄露並且可以被刪除,如果空閑時間超過removeAbandonedTimeout.
設置為true可以為寫法糟糕的沒有關閉連接的程序修復數據庫連接.
removeAbandonedTimeout 300 泄露的連接可以被刪除的超時值, 單位秒
logAbandoned false 標記當Statement或連接被泄露時是否打印程序的stack traces日志。
被泄露的Statements和連接的日志添加在每個連接打開或者生成新的Statement,
因為需要生成stack trace。


如果開啟"removeAbandoned",那么連接在被認為泄露時可能被池回收. 這個機制在(getNumIdle() < 2)
and (getNumActive() > getMaxActive() - 3)時被觸發.
舉例當maxActive=20, 活動連接為18,空閑連接為1時可以觸發"removeAbandoned".
但是活動連接只有在沒有被使用的時間超過"removeAbandonedTimeout"時才被刪除,默認300秒.
在resultset中游歷不被計算為被使用.

需要注意的問題
1、並發問題
為了使連接管理服務具有最大的通用性,必須考慮多線程環境,即並發問題。這個問題相對比較好解決,因為各個語言自身提供了對並發管理的支持像java,c#等等,使用synchronized(java)、lock(C#)關鍵字即可確保線程是同步的。
2、事務處理
我們知道,事務具有原子性,此時要求對數據庫的操作符合“ALL-OR-NOTHING”原則,即對於一組SQL語句要么全做,要么全不做。
我們知道當2個線程公用一個連接Connection對象,而且各自都有自己的事務要處理時候,對於連接池是一個很頭疼的問題,因為即使Connection類提供了相應的事務支持,可是我們仍然不能確定那個數據庫操作是對應那個事務的,這是由於我們有2個線程都在進行事務操作而引起的。為此我們可以使用每一個事務獨占一個連接來實現,雖然這種方法有點浪費連接池資源但是可以大大降低事務管理的復雜性。
3、連接池的分配與釋放
連接池的分配與釋放,對系統的性能有很大的影響。合理的分配與釋放,可以提高連接的復用度,從而降低建立新連接的開銷,同時還可以加快用戶的訪問速度。
對於連接的管理可使用一個List。即把已經創建的連接都放入List中去統一管理。每當用戶請求一個連接時,系統檢查這個List中有沒有可以分配的連接。如果有就把那個最合適的連接分配給他(如何能找到最合適的連接文章將在關鍵議題中指出);如果沒有就拋出一個異常給用戶,List中連接是否可以被分配由一個線程來專門管理。
4、連接池的配置與維護
連接池中到底應該放置多少連接,才能使系統的性能最佳?系統可采取設置最小連接數(minConnection)和最大連接數(maxConnection)等參數來控制連接池中的連接。比方說,最小連接數是系統啟動時連接池所創建的連接數。如果創建過多,則系統啟動就慢,但創建后系統的響應速度會很快;如果創建過少,則系統啟動的很快,響應起來卻慢。這樣,可以在開發時,設置較小的最小連接數,開發起來會快,而在系統實際使用時設置較大的,因為這樣對訪問客戶來說速度會快些。最大連接數是連接池中允許連接的最大數目,具體設置多少,要看系統的訪問量,可通過軟件需求上得到。
如何確保連接池中的最小連接數呢?有動態和靜態兩種策略。動態即每隔一定時間就對連接池進行檢測,如果發現連接數量小於最小連接數,則補充相應數量的新連接,以保證連接池的正常運轉。靜態是發現空閑連接不夠時再去檢查。

JNDI:JNDI(Java Naming and Directory Interface)是一個應用程序設計的API,為開發人員提供了查找和訪問各種命名和目錄服務的通用、統一的接口,類似JDBC都是構建在抽象層上。

建立在JNDI的連接池DBCP C3P0 Druid區別:

DBCP:是 apache 上的一個 java 連接池項目,也是 tomcat 7.0之前使用連接池,但是DBCP飽受詬病:1.DBCP是單線程,為了保證線程安全會鎖整個連接池。2.DBCP性能不佳。3.DBCP太復雜,超過60個類。4.DBCP使用靜態接口,在JDK1.6編譯有問題。5.DBCP發展滯后。。。為此,Tomcat從7.0開始引入新的模塊:Tomcat jdbc pool(兼容DBCP)

C3P0:它實現了數據源和JNDI綁定,支持JDBC3規范和JDBC2的標准擴展。目前使用它的開源項目有Hibernate,Spring等。 可以自動清理Statement和ResultSet。

druid:阿里巴巴推出的國產數據庫連接池,據網上測試對比,比目前的DBCP或C3P0數據庫連接池性能更好

最主要區別:
dbcp:沒有自動的去回收空閑連接的功能
c3p0:有自動回收空閑連接功能
druid:性能最佳

---------------------------------Druid--------------------------------
package com.weikun.db;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.alibaba.druid.pool.DruidPooledConnection;

public class DruidDB{
private static DruidDB databasePool = null;
private static DruidDataSource dds = null;
static{
Properties properties = loadPropertyFile("druid.ini");
try {
dds = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e){
e.printStackTrace();
}
}
private DruidDB(){

}
public static synchronized DruidDB getInstance(){
if (null == databasePool){
databasePool = new DruidDB();
}
return databasePool;
}
public DruidPooledConnection getConnection() throws SQLException{
return dds.getConnection();
}
private static Properties loadPropertyFile(String fullFile){
String webRootPath = null;
if (null == fullFile || fullFile.equals(""))
throw new IllegalArgumentException("Properties file path can not be null : " + fullFile);
try {
webRootPath = java.net.URLDecoder.decode(DruidDB.class.getClassLoader().getResource("").getPath(),"utf-8");
} catch (UnsupportedEncodingException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
InputStream inputStream = null;
Properties p = null;
try {
System.out.println(webRootPath + File.separator + fullFile);
inputStream = new FileInputStream(new File(webRootPath + File.separator + fullFile));
p = new Properties();
p.load(inputStream);
} catch (FileNotFoundException e){
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
} finally{
try {
if (inputStream != null)
inputStream.close();
} catch (IOException e){
e.printStackTrace();
}
}
return p;
}
}
---------------------------------Druid.ini配置文件--------------------------------
#mysql數據庫
url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8
username=root
password=root
#filters=stat
#最大連接數量
maxActive=20
#初始化連接數量
initialSize=10
#超時等待時間以毫秒為單位
maxWait=12000
#最小空閑連接
minIdle=5
#校驗連接池中限制時間超過minEvictableIdleTimeMillis的連接對象
timeBetweenEvictionRunsMillis=6000
#連接在池中保持空閑而不被空閑連接回收器線程(如果有)回收的最小時間值,單位毫秒
#minEvictableIdleTimeMillis=
#SQL查詢,用來驗證從連接池取出的連接,在將連接返回給調用者之前
validationQuery=SELECT now();
#指明連接是否被空閑連接回收器(如果有)進行檢驗.
#如果檢測失敗,則連接將被從池中去除.
testWhileIdle=true
#指明是否在從池中取出連接前進行檢驗,如果檢驗失敗,則從池中去除連接並嘗試取出另一個.
testOnBorrow=false
#指明是否在歸還到池中前進行檢驗
testOnReturn=false
#poolPreparedStatements=true
#maxPoolPreparedStatementPerConnectionSize=20

---------------------------------DBCP--------------------------------

package com.weikun.db;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;

public class DBCPDB {
private static Connection conn=null;
private static String driver="org.gjt.mm.mysql.Driver";
private static DataSource myDataSource=null;
//單例設計
private DBCPDB(){}
//靜態代碼塊在類載入java虛擬機時即執行!
static{
try {
Properties prop=new Properties();
prop.load(DBCPDB.class.getClassLoader().getResourceAsStream("dbcp.ini"));
myDataSource=BasicDataSourceFactory.createDataSource(prop);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//獲取連接
public static Connection getConnection() {
try {
conn= myDataSource.getConnection();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return conn;
}
//釋放資源
public static void closeConnection(){
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
---------------------------------DBCP.ini配置文件--------------------------------
#連接設置
driverClassName=org.gjt.mm.mysql.Driver
url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8
username=root
password=root
#<!-- 初始化連接 -->
initialSize=10
#最大連接數量
maxActive=50
#<!-- 最大空閑連接 -->
maxIdle=20
#<!-- 最小空閑連接 -->
minIdle=5
#<!-- 超時等待時間以毫秒為單位 60000毫秒/1000等於60秒 -->
maxWait=60000
#JDBC驅動建立連接時附帶的連接屬性屬性的格式必須為這樣:[屬性名=property;]
#注意:"user" 與 "password" 兩個屬性會被明確地傳遞,因此這里不需要包含他們。
connectionProperties=useUnicode=true;characterEncoding=utf-8
#指定由連接池所創建的連接的自動提交(auto-commit)狀態。
defaultAutoCommit=true
#driver default 指定由連接池所創建的連接的只讀(read-only)狀態。
#如果沒有設置該值,則“setReadOnly”方法將不被調用。(某些驅動並不支持只讀模式,如:Informix)
defaultReadOnly=
#driver default 指定由連接池所創建的連接的事務級別(TransactionIsolation)。
#可用值為下列之一:


---------------------------------C3P0--------------------------------

package com.weikun.db;

import java.sql.Connection;
import java.sql.SQLException;
import com.mchange.v2.c3p0.ComboPooledDataSource;

public class C3P0DB {
private static Connection conn=null;
static{
ComboPooledDataSource ds = new ComboPooledDataSource("mysql");//調用c3p0-config.xml下mysql節內容
try {
conn =ds.getConnection();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static Connection getConnection(){
return conn;
}
}

---------------------------------c3p0-config.xml配置文件--------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="automaticTestTable">con_test</property>
<property name="checkoutTimeout">30000</property>
<property name="idleConnectionTestPeriod">30</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
<property name="maxStatements">200</property>
</default-config>
<named-config name="mysql">
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf-8
</property>
<property name="user">root</property>
<property name="password">root</property>
<property name="acquireIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">30</property>
</named-config>
</c3p0-config>

---------------------------------Tomcat jdbc pool--------------------------------

數據源配置:
<Resource name="jdbc/TestDB"
auth="Container"
type="javax.sql.DataSource"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
testWhileIdle="true"
testOnBorrow="true"
testOnReturn="false"
validationQuery="SELECT 1"
validationInterval="30000"
timeBetweenEvictionRunsMillis="30000"
maxActive="100"
minIdle="10"
maxWait="10000"
initialSize="10"
removeAbandonedTimeout="60"
removeAbandoned="true"
logAbandoned="true"
minEvictableIdleTimeMillis="30000"
jmxEnabled="true"
jdbcInterceptors=
"org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer"
username="root"
password="password"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/mysql"/>


異步獲取連接的方法:
Connection con = null;
try {
Future<Connection> future = datasource.getConnectionAsync();
while (!future.isDone()) {
System.out.println("Connection is not yet available. Do some background work");
try {
Thread.sleep(100); //simulate work
}catch (InterruptedException x) {
Thread.currentThread().interrupted();
}
}
con = future.get(); //should return instantly
Statement st = con.createStatement();
ResultSet rs = st.executeQuery("select * from user");


在獨立的應用中使用:
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.apache.tomcat.jdbc.pool.PoolProperties;

public class SimplePOJOExample {
public static void main(String[] args) throws Exception {
PoolProperties p = new PoolProperties();
p.setUrl("jdbc:mysql://localhost:3306/mysql");
p.setDriverClassName("com.mysql.jdbc.Driver");
p.setUsername("root");
p.setPassword("password");
p.setJmxEnabled(true);
p.setTestWhileIdle(false);
p.setTestOnBorrow(true);
p.setValidationQuery("SELECT 1");
p.setTestOnReturn(false);
p.setValidationInterval(30000);
p.setTimeBetweenEvictionRunsMillis(30000);
p.setMaxActive(100);
p.setInitialSize(10);
p.setMaxWait(10000);
p.setRemoveAbandonedTimeout(60);
p.setMinEvictableIdleTimeMillis(30000);
p.setMinIdle(10);
p.setLogAbandoned(true);
p.setRemoveAbandoned(true);
p.setJdbcInterceptors("org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;"+
"org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer");
DataSource datasource = new DataSource();
datasource.setPoolProperties(p);
Connection con = null;
try {
con = datasource.getConnection();
Statement st = con.createStatement();
ResultSet rs = st.executeQuery("select * from user");
int cnt = 1;
while (rs.next()) {
System.out.println((cnt++)+". Host:" +rs.getString("Host")+
" User:"+rs.getString("User")+" Password:"+rs.getString("Password"));
}
rs.close();
st.close();
} finally {
if (con!=null) try {con.close();}catch (Exception ignore) {}
}
}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM