1 背景
? ? ? ? 目前java應(yīng)用很少使用jdbc直接連接數(shù)據(jù)庫執(zhí)行sql了,且數(shù)據(jù)庫連接是創(chuàng)建讳苦、關(guān)閉比較耗時(shí)的資源带膜,池技術(shù)的引用數(shù)據(jù)源中是不可避免的。
使用數(shù)據(jù)庫連接池會提高應(yīng)用的性能鸳谜,但是如果配置失誤的話反而會適得其反膝藕,甚至引起應(yīng)用的宕機(jī)。
? ? ? ?前些日子發(fā)現(xiàn)有些明明很簡單的sql咐扭,很簡單芭挽,單次執(zhí)行很快滑废,但是有些時(shí)候執(zhí)行時(shí)間卻是數(shù)倍的變化。
2 設(shè)定與準(zhǔn)備
? ?設(shè)立單獨(dú)的測試數(shù)據(jù)庫袜爪,使用公司云主機(jī)
? ?一條sql蠕趁,執(zhí)行時(shí)間是68ms左右
? ?使用druid連接池進(jìn)行測試,配置:初始連接數(shù)辛馆、最小連接數(shù)妻导、最大連接數(shù)
? ?使用newFixedThreadPool連接池模擬并發(fā)情況
? ?監(jiān)控指標(biāo):連接池活動的連接、空閑的連接怀各,獲取連接的時(shí)間倔韭、sql執(zhí)行時(shí)間、執(zhí)行總時(shí)間瓢对,線程等待數(shù)據(jù)源等待的線程(ps:獲取改數(shù)量或影響連接池的性能寿酌,這里暫不考慮)
3 程序結(jié)果與解讀
程序:
```
public class ConnectTest {
public static ThreadLocal timelocal =new ThreadLocal<>();
? ? @Test
public void test_druidConnectPool()throws SQLException {
final MyDruidDataSource dataSource =new MyDruidDataSource();
? ? ? ? Properties dbProperties =new Properties();
? ? ? ? dbProperties.put("druid.initialSize", "3");
? ? ? ? dbProperties.put("druid.minIdle", "3");
? ? ? ? dbProperties.put("druid.maxActive", "15");
? ? ? ? dbProperties.put("druid.maxWait", "5000");
? ? ? ? dbProperties.put("druid.timeBetweenEvictionRunsMillis", "90000");
? ? ? ? dbProperties.put("druid.minEvictableIdleTimeMillis", "1800000");
? ? ? ? dbProperties.put("druid.testOnBorrow", "false");
? ? ? ? dbProperties.put("druid.testOnReturn", "false");
? ? ? ? dbProperties.put("druid.testWhileIdle", "true");
? ? ? ? dbProperties.put("druid.name", "true");
? ? ? ? dbProperties.put("druid.url", "jdbc:mysql:///sieve?zeroDateTimeBehavior=convertToNull");
? ? ? ? dbProperties.put("druid.username", "");
? ? ? ? dbProperties.put("druid.password", "");
? ? ? ? dbProperties.put("druid.driverClassName", "com.mysql.jdbc.Driver");
? ? ? ? dataSource.setConnectProperties(dbProperties);
? ? ? ? try {
dataSource.init();
? ? ? ? }catch (SQLException e) {
e.printStackTrace();
? ? ? ? }
// 監(jiān)控線程
? ? ? ? ScheduledExecutorService cron= Executors.newScheduledThreadPool(1);
? ? ? ? cron.scheduleAtFixedRate(new Runnable(){
@Override
public void run() {
System.out.println(dataSource);
//? ? ? ? ? ? ? ? System.out.println(dataSource.getWaitThreadCount());
? ? ? ? ? ? }
},0,500, TimeUnit.MILLISECONDS);
//? ? ? ? // 增加鏈接線程
//? ? ? ? ScheduledExecutorService cron1= Executors.newScheduledThreadPool(1);
//? ? ? ? cron1.scheduleAtFixedRate(new Runnable(){
//
//? ? ? ? ? ? @Override
//? ? ? ? ? ? public void run() {
//? ? ? ? ? ? ? ? System.out.println(dataSource);
//? ? ? ? ? ? ? ? int i = dataSource.getMaxActive();
//? ? ? ? ? ? ? ? dataSource.setMaxActive(i+1);
//? ? ? ? ? ? }
//? ? ? ? },0,1500, TimeUnit.MILLISECONDS);
? ? ? ? int count =10000;
? ? ? ? final CountDownLatch latch =new CountDownLatch(count);
? ? ? ? ExecutorService executorService =? Executors.newFixedThreadPool(15);
? ? ? ? for (int i =0; i < count; i++) {
final int index = i;
? ? ? ? ? ? executorService.submit(new Runnable() {
@Override
public void run() {
try {
ConnectTest.ExceTime time =new ConnectTest.ExceTime();
? ? ? ? ? ? ? ? ? ? ? ? timelocal.set(time);
? ? ? ? ? ? ? ? ? ? ? ? time.setIndex(index);
//? ? ? ? ? ? ? ? ? ? ? exceSqlQuery(dataSource, "SELECT * FROM sieve.po_detail limit 1;");
? ? ? ? ? ? ? ? ? ? ? ? exceSqlUpdate(dataSource, "");
? ? ? ? ? ? ? ? ? ? }catch (Throwable e) {
e.printStackTrace();
? ? ? ? ? ? ? ? ? ? }
latch.countDown();
? ? ? ? ? ? ? ? }
});
? ? ? ? }
try {
latch.await();
? ? ? ? }catch (InterruptedException e) {
e.printStackTrace();
? ? ? ? }
try {
Thread.sleep(5000);
? ? ? ? }catch (InterruptedException e) {
e.printStackTrace();
? ? ? ? }
}
public void exceSqlUpdate(MyDruidDataSource dataSource, String sql) {
Connection con =null;
? ? ? ? PreparedStatement ps =null;
? ? ? ? try {
con = dataSource.getConnection(timelocal);
? ? ? ? ? ? ps = con.prepareStatement(sql);
? ? ? ? ? ? timelocal.get().setStartSqlTime(new java.util.Date().getTime());
? ? ? ? ? ? ps.execute(sql);
? ? ? ? ? ? timelocal.get().setEndSqlTime(new java.util.Date().getTime());
? ? ? ? }catch (Exception e) {
e.printStackTrace();
? ? ? ? }finally {
if (ps !=null) {
try {
ps.close();
? ? ? ? ? ? ? ? }catch (SQLException e) {
e.printStackTrace();
? ? ? ? ? ? ? ? }
}
if (con !=null) {
try {
con.close();
? ? ? ? ? ? ? ? }catch (SQLException e) {
e.printStackTrace();
? ? ? ? ? ? ? ? }
}
}
timelocal.get().calculationTime();
? ? ? ? timelocal.remove();
? ? }
static class ExceTime {
private int index;
? ? ? ? private long getConTime;
? ? ? ? private long startSqlTime;
? ? ? ? private long endSqlTime;
? ? ? ? public int getIndex() {
return index;
? ? ? ? }
public void setIndex(int index) {
this.index = index;
? ? ? ? }
public long getGetConTime() {
return getConTime;
? ? ? ? }
public void setGetConTime(long getConTime) {
this.getConTime = getConTime;
? ? ? ? }
public long getStartSqlTime() {
return startSqlTime;
? ? ? ? }
public void setStartSqlTime(long startSqlTime) {
this.startSqlTime = startSqlTime;
? ? ? ? }
public long getEndSqlTime() {
return endSqlTime;
? ? ? ? }
public void setEndSqlTime(long endSqlTime) {
this.endSqlTime = endSqlTime;
? ? ? ? }
public void calculationTime(){
System.out.println("編號:"+index+",獲取con與開始sql距離:" + (startSqlTime - getConTime) +", sql執(zhí)行時(shí)間:" + (endSqlTime - startSqlTime)
+",總時(shí)間:"+(endSqlTime - getConTime));
? ? ? ? }
}
public void exceSqlQuery(MyDruidDataSource dataSource, String sql) {
Connection con =null;
? ? ? ? PreparedStatement ps =null;
? ? ? ? try {
con = dataSource.getConnection();
? ? ? ? ? ? ps = con.prepareStatement(sql);
? ? ? ? ? ? ResultSet resultSet = ps.executeQuery(sql);
? ? ? ? ? ? analysisResultSet(resultSet);
? ? ? ? }catch (Exception e) {
e.printStackTrace();
? ? ? ? }finally {
if (ps !=null) {
try {
ps.close();
? ? ? ? ? ? ? ? }catch (SQLException e) {
e.printStackTrace();
? ? ? ? ? ? ? ? }
}
if (con !=null) {
try {
con.close();
? ? ? ? ? ? ? ? }catch (SQLException e) {
e.printStackTrace();
? ? ? ? ? ? ? ? }
}
}
}
private void analysisResultSet(ResultSet resultSet)throws SQLException {
List> objectList =new ArrayList<>();
? ? ? ? //獲得ResultSetMeataData對象
? ? ? ? ResultSetMetaData rsmd =null;
? ? ? ? rsmd = resultSet.getMetaData();
? ? ? ? int total_rows = rsmd.getColumnCount();
? ? ? ? while (resultSet.next()) {
List list =new ArrayList<>();
? ? ? ? ? ? //判斷數(shù)據(jù)類型&獲取value
? ? ? ? ? ? for (int i =0; i < total_rows; i++) {
String columnName = rsmd.getColumnLabel(i +1);
? ? ? ? ? ? ? ? try {
list.add(resultSet.getObject(columnName));
? ? ? ? ? ? ? ? }catch (Exception e) {
e.printStackTrace();
? ? ? ? ? ? ? ? }
}
objectList.add(list);
? ? ? ? }
System.out.println(Arrays.deepToString(objectList.toArray()));
? ? }
}
```
```
public class MyDruidDataSourceextends DruidDataSource {
public DruidPooledConnection getConnection(ThreadLocal local)throws SQLException {
local.get().setGetConTime(new Date().getTime());
? ? ? ? DruidPooledConnection connection =super.getConnection();
? ? ? ? return connection;
? ? }
}
```
a:
配置
初始連接數(shù)=3、最小連接數(shù)=3硕蛹、最大連接數(shù)=15 ? 并發(fā)線程=15 任務(wù)量=10000
結(jié)果
?????????????執(zhí)行總時(shí)間:4分10s ?
? ? ? ? ? ? 獲取連接平均時(shí)間:300ms左右醇疼,有少量600ms+ ??
????????????機(jī)器性能:cpu使用增加1%- 2% ?負(fù)載略有增加
總結(jié)
并發(fā)15對于連接池是3來說,是有一定獲取壓力的法焰,獲取對執(zhí)行sql有一定影響秧荆,時(shí)間影響為4倍
附件
b:
配置
初始連接數(shù)=3、最小連接數(shù)=3埃仪、最大連接數(shù)=3 ? 并發(fā)線程=50 任務(wù)量=10000
結(jié)果
執(zhí)行總時(shí)間:4分10s ?
獲取連接平均時(shí)間:1s+,有少量2s+乙濒,峰值5s+
機(jī)器性能:cpu使用增加1%- 2% ?負(fù)載略有增加,與a持平
總結(jié)
并發(fā)量50對于連接池?cái)?shù)量為3來說卵蛉,壓力巨大颁股,獲取對執(zhí)行sql有巨大壓力,時(shí)間影響為10+倍
附件
c:
配置
初始連接數(shù)=3傻丝、最小連接數(shù)=3甘有、最大連接數(shù)=15 ? 并發(fā)線程=15 任務(wù)量=10000
結(jié)果
執(zhí)行總時(shí)間:1分2s ?
獲取連接平均時(shí)間:1ms,在開始500次執(zhí)行的過程中有少量200ms葡缰,當(dāng)連接數(shù)=并發(fā)數(shù)時(shí)=15時(shí)亏掀,獲取連接不需要耗費(fèi)時(shí)間。
機(jī)器性能:cpu使用增加4.7%-5.3%泛释,負(fù)載略有增加滤愕,與a相比增加5倍cpu使用率。
總結(jié)
連接池的連接動態(tài)增加會影響sql的執(zhí)行總時(shí)間胁澳,推薦:初始數(shù)=最小數(shù)=最大連接數(shù)该互。
當(dāng)連接數(shù)=并發(fā)數(shù)時(shí)=15時(shí),獲取連接不需要耗費(fèi)時(shí)間韭畸。
當(dāng)連接數(shù)增加時(shí)宇智,意味著數(shù)據(jù)庫會執(zhí)行更多的任務(wù)蔓搞,cpu使用率、負(fù)載都會升高随橘。
附件
4 總結(jié)
?可以看到喂分,數(shù)據(jù)庫連接池的設(shè)置會sql的執(zhí)行、數(shù)據(jù)庫的性能都是有很到影響的机蔗,過小會增加獲取連接的開銷蒲祈、過大會影響數(shù)據(jù)庫的cpu使用率。
當(dāng)然影響數(shù)據(jù)庫性能的因素還有很多萝嘁,流量也是一個很重要的方便梆掸,過大的流量也會使db宕機(jī),連接池也是對sql流量的一種限流措施牙言。qps與db性能要綜合考慮酸钦。
如果流量不大的話,并發(fā)=連接數(shù)可以達(dá)到qps的最大咱枉。