為什么說Druid是目前最好的數(shù)據(jù)庫連接池?
一、介紹
數(shù)據(jù)庫連接是一項(xiàng)非常關(guān)鍵的、有限的、昂貴的資源,這一點(diǎn)在多用戶的網(wǎng)頁應(yīng)用程序中體現(xiàn)得尤為突出。
記得之前做的一個(gè)項(xiàng)目,當(dāng)時(shí)的應(yīng)用程序配置的數(shù)據(jù)庫連接池,最大允許的連接數(shù)是500,結(jié)果上線沒多久,并發(fā)量直接上來了,導(dǎo)致大量的數(shù)據(jù)插入失敗,當(dāng)晚的心情可想而知~
從那一次事故之后,讓我對(duì)應(yīng)用程序的數(shù)據(jù)庫連接數(shù)有了一次深刻的認(rèn)識(shí),為了防止再次栽跟頭,之后特意抽了一個(gè)時(shí)間來編寫程序測試案例,用于測試各個(gè)數(shù)據(jù)源連接池的穩(wěn)定性!
話不多說,直接擼起來!
二、程序?qū)嵗?/span>
熟悉 web 系統(tǒng)開發(fā)的同學(xué),基本都知道,在 Java 生態(tài)中開源的常用數(shù)據(jù)庫連接池有以下幾種:
dbcp: DBCP是一個(gè)依賴Jakarta commons-pool對(duì)象池機(jī)制的數(shù)據(jù)庫連接池,DBCP可以直接的在應(yīng)用程序中使用,Tomcat的數(shù)據(jù)源使用的就是DBCPc3p0: c3p0是一個(gè)開放源代碼的JDBC連接池,它在lib目錄中與Hibernate一起發(fā)布,包括了實(shí)現(xiàn)jdbc3和jdbc2擴(kuò)展規(guī)范說明的Connection和Statement池的DataSources對(duì)象druid:阿里出品,淘寶和支付寶專用數(shù)據(jù)庫連接池,但它不僅僅是一個(gè)數(shù)據(jù)庫連接池,它還包含一個(gè) ProxyDriver,一系列內(nèi)置的JDBC組件庫,一個(gè)SQL Parser。支持所有JDBC兼容的數(shù)據(jù)庫,包括Oracle、MySql、Derby、Postgresql、SQL Server、H2等等。
今天我們就一起來對(duì)比一下,這三種數(shù)據(jù)源連接池的穩(wěn)定性。
2.1、創(chuàng)建測試表
下面以 mysql 數(shù)據(jù)庫為例,創(chuàng)建一個(gè)t_test表,方面后續(xù)進(jìn)行插入數(shù)據(jù)操作。
CREATE TABLE t_test (
id bigint(20) unsigned NOT NULL COMMENT '主鍵ID',
name varchar(32) NOT NULL COMMENT '名稱',
PRIMARY KEY (id)
) ENGINE=InnoDB COMMENT='測試表';
2.2、 編寫測試用例
以dbcp為例,首先創(chuàng)建一個(gè)dbcp-jdbc.properties配置文件。
username=root
password=Hello@123456
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.31.200:3306/testdb?useUnicode=true&characterEncoding=UTF-8
initialSize=5
maxActive=1000
maxIdle=5
removeAbandoned=ture
removeAbandonedTimeout=20
logAbandoned=true
maxWait=100
接著,創(chuàng)建一個(gè)連接池工具DbcpJdbcUtil。
public class DbcpJdbcUtil {
private static final Logger logger = LoggerFactory.getLogger(DbcpJdbcUtil.class);
/**jdbc配置文件*/
private static Properties prop = new Properties();
private static BasicDataSource dataSource = null;
// 它是事務(wù)專用連接!
private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
static {
classPathSourceRead();
}
private static void classPathSourceRead(){
//讀取指定位置的配置文檔(讀取class目錄文件)
try {
logger.info("jdbc路徑:" + SysConstants.getValue());
prop.load(DbcpJdbcUtil.class.getClassLoader().getResourceAsStream(SysConstants.getValue()));
logger.info("數(shù)據(jù)配置信息" + JSON.toJSONString(prop));
logger.info("初始化默認(rèn)jdbc配置文件成功!");
} catch (Exception e) {
logger.error("初始化默認(rèn)jdbc文件失敗!",e);
}
}
/**
* 從連接池獲取數(shù)據(jù)源
* @return
* @throws Exception
*/
public static BasicDataSource getDataSource() throws Exception {
try {
if (dataSource == null) {
synchronized (DbcpJdbcUtil.class) {
if (dataSource == null) {
dataSource = new BasicDataSource();
dataSource.setUsername(prop.getProperty("username"));
dataSource.setPassword(prop.getProperty("password"));
dataSource.setDriverClassName(prop.getProperty("driverClassName"));
dataSource.setUrl(prop.getProperty("url"));
dataSource.setInitialSize(Integer.valueOf(prop.getProperty("initialSize")));
dataSource.setMaxActive(Integer.valueOf(prop.getProperty("maxActive")));
dataSource.setMaxIdle(Integer.valueOf(prop.getProperty("maxIdle")));
dataSource.setRemoveAbandoned(Boolean.valueOf(prop.getProperty("removeAbandoned")));
dataSource.setRemoveAbandonedTimeout(Integer.valueOf(prop.getProperty("removeAbandonedTimeout")));
dataSource.setLogAbandoned(Boolean.valueOf(prop.getProperty("logAbandoned")));
dataSource.setMaxWait(Integer.valueOf(prop.getProperty("maxWait")));
}
}
}
return dataSource;
} catch (Exception e) {
logger.error("根據(jù)數(shù)據(jù)庫名稱獲取數(shù)據(jù)庫資源失敗," , e);
throw new Exception("根據(jù)數(shù)據(jù)庫名稱獲取數(shù)據(jù)庫資源失敗");
}
}
/**
* 使用連接池返回一個(gè)連接對(duì)象
*
* @return
* @throws SQLException
*/
public static Connection getConnection() throws Exception {
try {
Connection con = tl.get();
// 當(dāng)con不等于null,說明已經(jīng)調(diào)用過beginTransaction(),表示開啟了事務(wù)!
if (con != null)
return con;
return getDataSource().getConnection();
} catch (Exception e) {
logger.error("獲取數(shù)據(jù)庫連接失敗!", e);
throw new SQLException("獲取數(shù)據(jù)庫連接失敗!");
}
}
/**
* 開啟事務(wù) 1. 獲取一個(gè)Connection,設(shè)置它的setAutoComnmit(false)
* 2. 還要保證dao中使用的連接是我們剛剛創(chuàng)建的! --------------
* 3. 創(chuàng)建一個(gè)Connection,設(shè)置為手動(dòng)提交
* 4. 把這個(gè)Connection給dao用!
* 5. 還要讓commitTransaction或rollbackTransaction可以獲取到!
*
* @throws SQLException
*/
public static void beginTransaction() throws Exception {
try {
Connection con = tl.get();
if (con != null) {
con.close();
tl.remove();
//throw new SQLException("已經(jīng)開啟了事務(wù),就不要重復(fù)開啟了!");
}
con = getConnection();
con.setAutoCommit(false);
tl.set(con);
} catch (Exception e) {
logger.error("數(shù)據(jù)庫事物開啟失敗!", e);
throw new SQLException("數(shù)據(jù)庫事物開啟失敗!");
}
}
/**
* 提交事務(wù) 1. 獲取beginTransaction提供的Connection,然后調(diào)用commit方法
*
* @throws SQLException
*/
public static void commitTransaction() throws SQLException {
Connection con = tl.get();
try {
if (con == null)
throw new SQLException("還沒有開啟事務(wù),不能提交!");
con.commit();
} catch (Exception e) {
logger.error("數(shù)據(jù)庫事物提交失敗!", e);
throw new SQLException("數(shù)據(jù)庫事物提交失敗!");
} finally {
if (con != null) {
con.close();
}
tl.remove();
}
}
/**
* 回滾事務(wù) 1. 獲取beginTransaction提供的Connection,然后調(diào)用rollback方法
*
* @throws SQLException
*/
public static void rollbackTransaction() throws SQLException {
Connection con = tl.get();
try {
if (con == null)
throw new SQLException("還沒有開啟事務(wù),不能回滾!");
con.rollback();
} catch (Exception e) {
logger.error("數(shù)據(jù)庫事物回滾失敗!", e);
throw new SQLException("數(shù)據(jù)庫事物回滾失敗!");
} finally {
if (con != null) {
con.close();
}
tl.remove();
}
}
/**
* 釋放連接
* @param connection
* @throws SQLException
*/
public static void releaseConnection(Connection connection) throws SQLException {
try {
Connection con = tl.get();
// 判斷它是不是事務(wù)專用,如果是,就不關(guān)閉! 如果不是事務(wù)專用,那么就要關(guān)閉!
// 如果con == null,說明現(xiàn)在沒有事務(wù),那么connection一定不是事務(wù)專用的!
//如果con != null,說明有事務(wù),那么需要判斷參數(shù)連接是否與con相等,若不等,說明參數(shù)連接不是事務(wù)專用連接
if (con == null || con != connection)
connection.close();
} catch (Exception e) {
logger.error("數(shù)據(jù)庫連接釋放失敗!", e);
throw new SQLException("數(shù)據(jù)庫連接釋放失敗!");
}
}
}
最后,編寫單元測試程序DBCPTest。
public class DBCPTest {
private static final int sumCount = 1000000;
private static final int threadNum = 600;
private void before(String path) {
SysConstants.putValue(path);
new DBCPService().insert("delete from t_test");
}
@Test
public void testMysql() {
long start = System.currentTimeMillis();
String path = "config/mysql/dbcp-jdbc.properties";
before(path);
for (int i =0; i < 1; i++) {
String sql = "insert into t_test(id,name) values('" +i+ "','dbcp-mysql-" + i + "')";
new DBCPService().insert(sql);
}
System.out.println("耗時(shí):" + (System.currentTimeMillis() - start));
}
@Test
public void testThreadMysql() throws InterruptedException {
String path = "config/mysql/dbcp-jdbc.properties";
before(path);
BlockingQueue<String> queue = new LinkedBlockingQueue<String>();
for (int i = 0; i < sumCount; i++) {
String sql = "insert into t_test(id,name) values('" +i+ "','dbcp-mysql-" + i + "')";
queue.put(sql);
}
long start = System.currentTimeMillis();
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for (int i = 0; i < threadNum; i++) {
final int finalI = i + 1;
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread " + finalI + " start");
boolean isGo = true;
while (isGo) {
String sql = queue.poll();
if(sql != null) {
new DBCPService().insert(sql);
}else {
isGo =false;
System.out.println("thread " + finalI + " finish");
countDownLatch.countDown();
}
}
}
}).start();
}
countDownLatch.await();
System.out.println("耗時(shí):" + (System.currentTimeMillis() - start));
}
}
c3p0、druid的配置也類似,這里就不在重復(fù)介紹了!
三、性能測試
程序編寫完成之后,下面我們就一起來結(jié)合各種不同的場景來測試一下各個(gè)數(shù)據(jù)連接池的表現(xiàn)。
為了進(jìn)一步擴(kuò)大測試范圍,本次測試還將各個(gè)主流的數(shù)據(jù)庫也拉入進(jìn)去,測試的數(shù)據(jù)庫分別是:mysql-5.7、oracle-12、postgresql-9.6
3.1、插入10萬條數(shù)據(jù)
首先,我們來測試一下,各個(gè)數(shù)據(jù)庫插入10萬條數(shù)據(jù),采用不同的數(shù)據(jù)源連接池,看看它們的表現(xiàn)如何?
測試 dbcp執(zhí)行結(jié)果

測試 c3p0執(zhí)行結(jié)果

測試 druid執(zhí)行結(jié)果

從上面測試結(jié)果,我們可以基本得出如下結(jié)論:
從數(shù)據(jù)連接池性能角度看: dbcp>druid>c3p0從數(shù)據(jù)庫性能角度看: oracle>postgresql>mysql
其中druid對(duì)postgresql的支持性能最好,c3p0的表現(xiàn)比較差!
3.2、插入100萬條數(shù)據(jù)
可能有的同學(xué),還不太認(rèn)可,下面我們就來測試一下插入100萬條,看看它們的表現(xiàn)如何?
測試 dbcp執(zhí)行結(jié)果

測試 c3p0執(zhí)行結(jié)果

測試 druid執(zhí)行結(jié)果

從上面測試結(jié)果,我們可以基本得出如下結(jié)論:
從數(shù)據(jù)連接池性能角度看: druid性能比較穩(wěn)定,dbcp、c3p0都有某種程度的執(zhí)行失敗從數(shù)據(jù)庫性能角度看: postgresql>oracle>mysql
還是一樣的結(jié)論,druid對(duì)postgresql的支持性能最好,c3p0的表現(xiàn)比較差!
四、小結(jié)
從上面的測試結(jié)果,我們可以很清晰的看到,在數(shù)據(jù)連接池方面,druid和dbcp旗鼓相當(dāng),而并發(fā)方面druid的穩(wěn)定性大于dbcp,c3p0相比druid和dbcp,穩(wěn)定性和執(zhí)行速度要弱些。
在數(shù)據(jù)庫方面,postgresql速度要優(yōu)于oracle,而oracle對(duì)各個(gè)數(shù)據(jù)源的支持和穩(wěn)定性要有優(yōu)勢,mysql相比oracle和postgresql,執(zhí)行速度要弱些。
如果在實(shí)際開發(fā)中,數(shù)據(jù)源連接池推薦采用druid,數(shù)據(jù)庫的選用方面 postgresql > oracle > mysql。
程序汪資料鏈接
程序汪接的7個(gè)私活都在這里,經(jīng)驗(yàn)整理
Java項(xiàng)目分享 最新整理全集,找項(xiàng)目不累啦 07版
堪稱神級(jí)的Spring Boot手冊(cè),從基礎(chǔ)入門到實(shí)戰(zhàn)進(jìn)階
臥槽!字節(jié)跳動(dòng)《算法中文手冊(cè)》火了,完整版 PDF 開放下載!
臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開放下載!
字節(jié)跳動(dòng)總結(jié)的設(shè)計(jì)模式 PDF 火了,完整版開放下載!
歡迎添加程序汪個(gè)人微信 itwang009 進(jìn)粉絲群或圍觀朋友圈
