driud调优

基础参数说明

由于DruidDataSource大部分属性都是参考DBCP的
所以可以参考下DBCP配置说明

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
        init-method="init" destroy-method="close">
        <!-- 基本属性 url、user、password -->
        <property name="url" value="${jdbc_url}" />
        <property name="username" value="${jdbc_user}" />
        <property name="password" value="${jdbc_password}" />

        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="5" />
        <property name="minIdle" value="5" />
        <property name="maxActive" value="10" />
        <!-- 配置从连接池获取连接等待超时的时间 -->
        <property name="maxWait" value="10000" />

        <!-- 配置间隔多久启动一次DestroyThread,对连接池内的连接才进行一次检测,单位是毫秒。
            检测时:1.如果连接空闲并且超过minIdle以外的连接,如果空闲时间超过minEvictableIdleTimeMillis设置的值则直接物理关闭。2.在minIdle以内的不处理。
        -->
        <property name="timeBetweenEvictionRunsMillis" value="600000" />
        <!-- 配置一个连接在池中最大空闲时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000" />
        <!-- 设置从连接池获取连接时是否检查连接有效性,true时,每次都检查;false时,不检查 -->
        <property name="testOnBorrow" value="false" />
        <!-- 设置往连接池归还连接时是否检查连接有效性,true时,每次都检查;false时,不检查 -->
        <property name="testOnReturn" value="false" />
        <!-- 设置从连接池获取连接时是否检查连接有效性,true时,如果连接空闲时间超过minEvictableIdleTimeMillis进行检查,否则不检查;false时,不检查 -->
        <property name="testWhileIdle" value="true" />
        <!-- 检验连接是否有效的查询语句。如果数据库Driver支持ping()方法,则优先使用ping()方法进行检查,否则使用validationQuery查询进行检查。(Oracle jdbc Driver目前不支持ping方法) -->
        <property name="validationQuery" value="select 1 from dual" />
        <!-- 单位:秒,检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法 -->
        <!-- <property name="validationQueryTimeout" value="1" />  -->

        <!-- 打开后,增强timeBetweenEvictionRunsMillis的周期性连接检查,minIdle内的空闲连接,每次检查强制验证连接有效性. 参考:https://github.com/alibaba/druid/wiki/KeepAlive_cn -->
        <property name="keepAlive" value="true" />  

        <!-- 连接泄露检查,打开removeAbandoned功能 , 连接从连接池借出后,长时间不归还,将触发强制回连接。回收周期随timeBetweenEvictionRunsMillis进行,如果连接为从连接池借出状态,并且未执行任何sql,并且从借出时间起已超过removeAbandonedTimeout时间,则强制归还连接到连接池中。 -->
        <property name="removeAbandoned" value="true" /> 
        <!-- 超时时间,秒 -->
        <property name="removeAbandonedTimeout" value="80"/>
        <!-- 关闭abanded连接时输出错误日志,这样出现连接泄露时可以通过错误日志定位忘记关闭连接的位置 -->
        <property name="logAbandoned" value="true" />

        <!-- 根据自身业务及事务大小来设置 -->
        <property name="connectionProperties"
            value="oracle.net.CONNECT_TIMEOUT=2000;oracle.jdbc.ReadTimeout=10000"></property>

        <!-- 打开PSCache,并且指定每个连接上PSCache的大小,Oracle等支持游标的数据库,打开此开关,会以数量级提升性能,具体查阅PSCache相关资料 -->
        <property name="poolPreparedStatements" value="true" />
        <property name="maxPoolPreparedStatementPerConnectionSize"
            value="20" />   

        <!-- 配置监控统计拦截的filters -->
        <!-- <property name="filters" value="stat,slf4j" /> -->

        <property name="proxyFilters">
            <list>
                <ref bean="log-filter" />
                <ref bean="stat-filter" />
            </list>
        </property>
        <!-- 配置监控统计日志的输出间隔,单位毫秒,每次输出所有统计数据会重置,酌情开启 -->
        <property name="timeBetweenLogStatsMillis" value="120000" />
    </bean>

druid数据源检测数据库连接有效性testOnBorrow、testOnReturn、testWhileIdle属性原理分析

druid多数据源检测数据库连接的有效性属性配置如下:
#mysql默认使用ping模式,可以通过设置系统属性System.getProperties().setProperty("druid.mysql.usePingMethod", "false")更改为sql模式
#用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。默认:SELECT 1
spring.emily.datasource.config.mysql.validation-query="SELECT 1"
#单位:秒,检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法,默认:-1
spring.emily.datasource.config.mysql.validation-query-timeout=-1
#申请连接时执行validationQuery检测连接是否有效,这个配置会降低性能。默认:false(如果test-on-borrow为true,那么test-while-idle无效)
spring.emily.datasource.config.mysql.test-on-borrow=false
#建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。默认:true
spring.emily.datasource.config.mysql.test-while-idle=true
#归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。默认:false
spring.emily.datasource.config.mysql.test-on-return=false
获取数据库连接DruidPooledConnection对象入口代码com.alibaba.druid.pool.DruidDataSource#getConnection()
 1     @Override
 2     public DruidPooledConnection getConnection() throws SQLException {
 3         return getConnection(maxWait);
 4     }
 5 
 6     public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
 7       //对数据库连接池进行初始化  
 8       init();
 9 
10         if (filters.size() > 0) {
11             FilterChainImpl filterChain = new FilterChainImpl(this);
12             return filterChain.dataSource_connect(this, maxWaitMillis);
13         } else {
14             //获取数据库连接对象
15             return getConnectionDirect(maxWaitMillis);
16         }
17     }
com.alibaba.druid.pool.DruidDataSource#init初始化方法会对ValidConnectionChecker接口的实现类进行初始化
 1     private void initValidConnectionChecker() {
 2         if (this.validConnectionChecker != null) {
 3             return;
 4         }
 5 
 6         String realDriverClassName = driver.getClass().getName();
 7         if (JdbcUtils.isMySqlDriver(realDriverClassName)) {
 8             this.validConnectionChecker = new MySqlValidConnectionChecker();
 9 
10         } else if (realDriverClassName.equals(JdbcConstants.ORACLE_DRIVER)
11                 || realDriverClassName.equals(JdbcConstants.ORACLE_DRIVER2)) {
12             this.validConnectionChecker = new OracleValidConnectionChecker();
13 
14         } else if (realDriverClassName.equals(JdbcConstants.SQL_SERVER_DRIVER)
15                    || realDriverClassName.equals(JdbcConstants.SQL_SERVER_DRIVER_SQLJDBC4)
16                    || realDriverClassName.equals(JdbcConstants.SQL_SERVER_DRIVER_JTDS)) {
17             this.validConnectionChecker = new MSSQLValidConnectionChecker();
18 
19         } else if (realDriverClassName.equals(JdbcConstants.POSTGRESQL_DRIVER)
20                 || realDriverClassName.equals(JdbcConstants.ENTERPRISEDB_DRIVER)
21                 || realDriverClassName.equals(JdbcConstants.POLARDB_DRIVER)) {
22             this.validConnectionChecker = new PGValidConnectionChecker();
23         }
24     }

此方法分别对不同种类的数据库连接检测有效性的实现类进行初始化,接下来会在建立连接、归还链接过程中用到;

com.alibaba.druid.pool.DruidDataSource#getConnectionDirect方法是获取数据库连接的具体实现:
 1     public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
 2         int notFullTimeoutRetryCnt = 0;
 3         for (;;) {
 4             // handle notFullTimeoutRetry
 5             DruidPooledConnection poolableConnection;
 6             try {
 7                //从数据库连接池中获取连接对象句柄
 8                 poolableConnection = getConnectionInternal(maxWaitMillis);
 9             } catch (GetConnectionTimeoutException ex) {
10                 if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) {
11                     notFullTimeoutRetryCnt++;
12                     if (LOG.isWarnEnabled()) {
13                         LOG.warn("get connection timeout retry : " + notFullTimeoutRetryCnt);
14                     }
15                     continue;
16                 }
17                 throw ex;
18             }
19                         //如果开启了获取连接有效性检查,则进行有效性检查
20             if (testOnBorrow) {
21                //有效性检查
22                 boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
23                 if (!validate) {
24                     if (LOG.isDebugEnabled()) {
25                         LOG.debug("skip not validate connection.");
26                     }
27 
28                     discardConnection(poolableConnection.holder);
29                     continue;
30                 }
31             } else {
32                 if (poolableConnection.conn.isClosed()) {
33                     discardConnection(poolableConnection.holder); // 传入null,避免重复关闭
34                     continue;
35                 }
36                                 //如果testOnBorrow为false,且testWhileIdle为ture,则在判定连接在空闲时间大于timeBetweenEvictionRunsMillis(心跳检查时间)时才做判定,对性能损耗几乎为0
37                 if (testWhileIdle) {
38                     final DruidConnectionHolder holder = poolableConnection.holder;
39                     long currentTimeMillis             = System.currentTimeMillis();
40                     long lastActiveTimeMillis          = holder.lastActiveTimeMillis;
41                     long lastExecTimeMillis            = holder.lastExecTimeMillis;
42                     long lastKeepTimeMillis            = holder.lastKeepTimeMillis;
43 
44                     if (checkExecuteTime
45                             && lastExecTimeMillis != lastActiveTimeMillis) {
46                         lastActiveTimeMillis = lastExecTimeMillis;
47                     }
48 
49                     if (lastKeepTimeMillis > lastActiveTimeMillis) {
50                         lastActiveTimeMillis = lastKeepTimeMillis;
51                     }
52 
53                     long idleMillis                    = currentTimeMillis - lastActiveTimeMillis;
54 
55                     long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;
56 
57                     if (timeBetweenEvictionRunsMillis <= 0) {
58                         timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
59                     }
60                                         //连接空闲时间大于等于守护线程中心跳间隔时间时才进行有效性检查
61                     if (idleMillis >= timeBetweenEvictionRunsMillis
62                             || idleMillis < 0 // unexcepted branch
63                             ) {
64                         boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
65                         if (!validate) {
66                             if (LOG.isDebugEnabled()) {
67                                 LOG.debug("skip not validate connection.");
68                             }
69 
70                             discardConnection(poolableConnection.holder);
71                              continue;
72                         }
73                     }
74                 }
75             }
76 
77             if (removeAbandoned) {
78                 StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
79                 poolableConnection.connectStackTrace = stackTrace;
80                 poolableConnection.setConnectedTimeNano();
81                 poolableConnection.traceEnable = true;
82 
83                 activeConnectionLock.lock();
84                 try {
85                     activeConnections.put(poolableConnection, PRESENT);
86                 } finally {
87                     activeConnectionLock.unlock();
88                 }
89             }
90 
91             if (!this.defaultAutoCommit) {
92                 poolableConnection.setAutoCommit(false);
93             }
94 
95             return poolableConnection;
96         }
97     }

com.alibaba.druid.pool.DruidAbstractDataSource#testConnectionInternal(com.alibaba.druid.pool.DruidConnectionHolder, java.sql.Connection)方法对从数据库中获取到的连接有效性进行检查

 1     protected boolean testConnectionInternal(DruidConnectionHolder holder, Connection conn) {
 2         String sqlFile = JdbcSqlStat.getContextSqlFile();
 3         String sqlName = JdbcSqlStat.getContextSqlName();
 4 
 5         if (sqlFile != null) {
 6             JdbcSqlStat.setContextSqlFile(null);
 7         }
 8         if (sqlName != null) {
 9             JdbcSqlStat.setContextSqlName(null);
10         }
11         try {
12             //validConnectionChecker属性是init方法中对各数据库驱动进行初始化的有效性检查实现类
13             if (validConnectionChecker != null) {
14                //对连接进行有效性检查
15                 boolean valid = validConnectionChecker.isValidConnection(conn, validationQuery, validationQueryTimeout);
16                ...
17     }
对mysql数据源进行连接有效性检查实现类MySqlValidConnectionChecker源码分析
  1 public class MySqlValidConnectionChecker extends ValidConnectionCheckerAdapter implements ValidConnectionChecker, Serializable {
  2 
  3     public static final int DEFAULT_VALIDATION_QUERY_TIMEOUT = 1;
  4     public static final String DEFAULT_VALIDATION_QUERY = "SELECT 1";
  5 
  6     private static final long serialVersionUID = 1L;
  7     private static final Log  LOG              = LogFactory.getLog(MySqlValidConnectionChecker.class);
  8 
  9     private Class<?> clazz;
 10     private Method   ping;
 11     
 12     private boolean  usePingMethod = false;
 13 
 14     public MySqlValidConnectionChecker(){
 15         try {
 16             //8.0之前的mysql驱动
 17             clazz = Utils.loadClass("com.mysql.jdbc.MySQLConnection");
 18             if (clazz == null) {
 19                //8.0之后的驱动
 20                 clazz = Utils.loadClass("com.mysql.cj.jdbc.ConnectionImpl");
 21             }
 22 
 23             if (clazz != null) {
 24                //获取pingInternal方法示例
 25                 ping = clazz.getMethod("pingInternal", boolean.class, int.class);
 26             }
 27 
 28             if (ping != null) {
 29                //实际是使用ping模式检测连接有效性
 30                 usePingMethod = true;
 31             }
 32         } catch (Exception e) {
 33             LOG.warn("Cannot resolve com.mysql.jdbc.Connection.ping method.  Will use 'SELECT 1' instead.", e);
 34         }
 35 
 36         configFromProperties(System.getProperties());
 37     }
 38 
 39     @Override
 40     public void configFromProperties(Properties properties) {
 41         if (properties == null) {
 42             return;
 43         }
 44                 //根据系统属性设置判定连接有效性的方法
 45         String property = properties.getProperty("druid.mysql.usePingMethod");
 46         if ("true".equals(property)) {
 47             setUsePingMethod(true);
 48         } else if ("false".equals(property)) {
 49             setUsePingMethod(false);
 50         }
 51     }
 52 
 53     public boolean isUsePingMethod() {
 54         return usePingMethod;
 55     }
 56 
 57     public void setUsePingMethod(boolean usePingMethod) {
 58         this.usePingMethod = usePingMethod;
 59     }
 60 
 61     public boolean isValidConnection(Connection conn, String validateQuery, int validationQueryTimeout) throws Exception {
 62         if (conn.isClosed()) {
 63             return false;
 64         }
 65                 
 66         if (usePingMethod) {
 67             if (conn instanceof DruidPooledConnection) {
 68                 conn = ((DruidPooledConnection) conn).getConnection();
 69             }
 70 
 71             if (conn instanceof ConnectionProxy) {
 72                 conn = ((ConnectionProxy) conn).getRawObject();
 73             }
 74 
 75             if (clazz.isAssignableFrom(conn.getClass())) {
 76                 if (validationQueryTimeout <= 0) {
 77                     validationQueryTimeout = DEFAULT_VALIDATION_QUERY_TIMEOUT;
 78                 }
 79 
 80                 try {
 81                   //使用ping的方式判定连接有效性
 82                     ping.invoke(conn, true, validationQueryTimeout * 1000);
 83                 } catch (InvocationTargetException e) {
 84                     Throwable cause = e.getCause();
 85                     if (cause instanceof SQLException) {
 86                         throw (SQLException) cause;
 87                     }
 88                     throw e;
 89                 }
 90                 return true;
 91             }
 92         }
 93 
 94         String query = validateQuery;
 95         if (validateQuery == null || validateQuery.isEmpty()) {
 96             query = DEFAULT_VALIDATION_QUERY;
 97         }
 98 
 99         Statement stmt = null;
100         ResultSet rs = null;
101         try {
102           
103           //使用sql的方式校验连接的有效性
104             stmt = conn.createStatement();
105             if (validationQueryTimeout > 0) {
106                 stmt.setQueryTimeout(validationQueryTimeout);
107             }
108             rs = stmt.executeQuery(query);
109             return true;
110         } finally {
111             JdbcUtils.close(rs);
112             JdbcUtils.close(stmt);
113         }
114 
115     }
116 
117 }

默认情况下是使用ping的方式判定连接的有效性,但是可以通过druid.mysql.usePingMethod系统属性修改为sql判定连接有效性模式。

com.alibaba.druid.pool.vendor.OracleValidConnectionChecker是oracle校验连接有效性的实现类

 1 public class OracleValidConnectionChecker extends ValidConnectionCheckerAdapter implements ValidConnectionChecker, Serializable {
 2 
 3     private static final long serialVersionUID     = -2227528634302168877L;
 4 
 5 
 6     private int               timeout              = 1;
 7 
 8     private String            defaultValidateQuery = "SELECT 'x' FROM DUAL";
 9 
10     public OracleValidConnectionChecker(){
11         configFromProperties(System.getProperties());
12     }
13 
14     @Override
15     public void configFromProperties(Properties properties) {
16         if (properties == null) {
17             return;
18         }
19                 //可以通过系统属性方式修改超时时间
20         String property = properties.getProperty("druid.oracle.pingTimeout");
21         if (property != null && property.length() > 0) {
22             int value = Integer.parseInt(property);
23             setTimeout(value);
24         }
25     }
26 
27     public void setTimeout(int seconds) {
28         this.timeout = seconds;
29     }
30         //采用sql模式校验有效性
31     public boolean isValidConnection(Connection conn, String validateQuery, int validationQueryTimeout) throws Exception {
32         if (validateQuery == null || validateQuery.isEmpty()) {
33             validateQuery = this.defaultValidateQuery;
34         }
35 
36         if (conn.isClosed()) {
37             return false;
38         }
39 
40         if (conn instanceof DruidPooledConnection) {
41             conn = ((DruidPooledConnection) conn).getConnection();
42         }
43 
44         if (conn instanceof ConnectionProxy) {
45             conn = ((ConnectionProxy) conn).getRawObject();
46         }
47 
48         if (validateQuery == null || validateQuery.isEmpty()) {
49             return true;
50         }
51 
52         int queryTimeout = validationQueryTimeout <= 0 ? timeout : validationQueryTimeout;
53 
54         Statement stmt = null;
55         ResultSet rs = null;
56         try {
57             stmt = conn.createStatement();
58             stmt.setQueryTimeout(queryTimeout);
59             rs = stmt.executeQuery(validateQuery);
60             return true;
61         } finally {
62             JdbcUtils.close(rs);
63             JdbcUtils.close(stmt);
64         }
65     }
66 }

oracle采用的是sql校验连接有效性的模式,默认超时时间是1s,可以通过系统属性druid.oracle.pingTimeout修改超时时间,也可以通过validation-query-timeout属性配置修改超时时间。

另外连接池会在com.alibaba.druid.pool.DruidDataSource#recycle方法中回收连接的时候通过属性testOnReturn判定是否需要判定连接的有效性。

销毁连接分析

在创建连接的init()方法,注意到创建了线程DestroyConnectionThread用来销毁连接的,线程循环是通过timeBetweenEvictionRunsMillis 参数来设置连接检查的时间间隔。

 1         public void run() {
 2             //计数器减一
 3             initedLatch.countDown();
 4 
 5             for (;;) {
 6                 // 从前面开始删除
 7                 try {
 8                     //如果连接池未关闭
 9                     if (closed || closing) {
10                         break;
11                     }
12                     //设置时间间隔休眠
13                     if (timeBetweenEvictionRunsMillis > 0) {
14                         Thread.sleep(timeBetweenEvictionRunsMillis);
15                     } else {
16                         Thread.sleep(1000); 
17                     }
18                     //如果发生中断异常则退出
19                     if (Thread.interrupted()) {
20                         break;
21                     }
22                     //运行销毁任务
23                     destroyTask.run();
24                 } catch (InterruptedException e) {
25                     break;
26                 }
27             }
28         }

DestroyTask

销毁任务类,通过DestroyConnectionThread线程直接调用,一旦有自定义销毁任务时,才会变为线程执行。

1         @Override
2         public void run() {
3             //检查连接
4             shrink(true, keepAlive);
5             //移出废弃的连接
6             if (isRemoveAbandoned()) {
7                 removeAbandoned();
8             }
9         }

shrink

该方法主要是检查连接,并销毁连接。

  1 public void shrink(boolean checkTime, boolean keepAlive) {
  2         //获取锁
  3         try {
  4             lock.lockInterruptibly();
  5         } catch (InterruptedException e) {
  6             return;
  7         }
  8 
  9         boolean needFill = false;
 10         int evictCount = 0;
 11         int keepAliveCount = 0;
 12         //计算错误增量
 13         int fatalErrorIncrement = fatalErrorCount - fatalErrorCountLastShrink;
 14         fatalErrorCountLastShrink = fatalErrorCount;
 15         
 16         try {
 17             //如果还未进行初始化,则进行init()
 18             if (!inited) {
 19                 return;
 20             }
 21             //剩余空闲连接数 = 连接池中连接数- 最小空闲连接数
 22             final int checkCount = poolingCount - minIdle;
 23             final long currentTimeMillis = System.currentTimeMillis();
 24             for (int i = 0; i < poolingCount; ++i) {
 25                 //从连接池中拿连接
 26                 DruidConnectionHolder connection = connections[i];
 27                 //(如果发生致命错误或者致命错误次数大于0)&& 最后致命错误时间大于当前连接的连接时间
 28                 if ((onFatalError || fatalErrorIncrement > 0) && (lastFatalErrorTimeMillis > connection.connectTimeMillis))  {
 29                     //设置为存活连接
 30                     keepAliveConnections[keepAliveCount++] = connection;
 31                     continue;
 32                 }
 33 
 34                 if (checkTime) {
 35                     //如果设置连接使用销毁时间,则判断当前连接使用时间是否大于这个时间,如果大于则直接将连接放入销毁的连接池中
 36                     if (phyTimeoutMillis > 0) {
 37                         long phyConnectTimeMillis = currentTimeMillis - connection.connectTimeMillis;
 38                         if (phyConnectTimeMillis > phyTimeoutMillis) {
 39                             evictConnections[evictCount++] = connection;
 40                             continue;
 41                         }
 42                     }
 43                     //计算空闲时间
 44                     long idleMillis = currentTimeMillis - connection.lastActiveTimeMillis;
 45                     //空闲时间 < 最小存活时间 && 空闲时间 < 保证活跃时间,退出循环检查
 46                     if (idleMillis < minEvictableIdleTimeMillis
 47                             && idleMillis < keepAliveBetweenTimeMillis
 48                     ) {
 49                         break;
 50                     }
 51                     //空闲时间 >= 最小存活时间,将连接放入销毁池中
 52                     if (idleMillis >= minEvictableIdleTimeMillis) {
 53                         if (checkTime && i < checkCount) {
 54                             evictConnections[evictCount++] = connection;
 55                             continue;
 56                         } else if (idleMillis > maxEvictableIdleTimeMillis) {
 57                             evictConnections[evictCount++] = connection;
 58                             continue;
 59                         }
 60                     }
 61                     //开启保活机制 && 空闲时间 >= 连接保活时间,将连接放入存活池中。
 62                     if (keepAlive && idleMillis >= keepAliveBetweenTimeMillis) {
 63                         keepAliveConnections[keepAliveCount++] = connection;
 64                     }
 65                 //如果未开启检查,则仅留下最小空闲连接。
 66                 } else {
 67                     if (i < checkCount) {
 68                         evictConnections[evictCount++] = connection;
 69                     } else {
 70                         break;
 71                     }
 72                 }
 73             }
 74             //删除数量 = 销毁连接数量+ 活跃连接数量
 75             int removeCount = evictCount + keepAliveCount;
 76             if (removeCount > 0) {
 77                 System.arraycopy(connections, removeCount, connections, 0, poolingCount - removeCount);
 78                 Arrays.fill(connections, poolingCount - removeCount, poolingCount, null);
 79                 //空闲连接池数量 = 空闲连接池数量-删除数量
 80                 poolingCount -= removeCount;
 81             }
 82             //连接池检查数量 = 连接池检查数量 + 活跃连接数量
 83             keepAliveCheckCount += keepAliveCount;
 84             //
 85             if (keepAlive && poolingCount + activeCount < minIdle) {
 86                 needFill = true;
 87             }
 88         } finally {
 89             lock.unlock();
 90         }
 91         //如果销毁数量大于0,则关闭这些连接
 92         if (evictCount > 0) {
 93             for (int i = 0; i < evictCount; ++i) {
 94                 DruidConnectionHolder item = evictConnections[i];
 95                 Connection connection = item.getConnection();
 96                 JdbcUtils.close(connection);
 97                 destroyCountUpdater.incrementAndGet(this);
 98             }
 99             Arrays.fill(evictConnections, null);
100         }
101         //如果活跃连接数量 >0 ,则对连接进行校验
102         if (keepAliveCount > 0) {
103             // keep order
104             for (int i = keepAliveCount - 1; i >= 0; --i) {
105                 DruidConnectionHolder holer = keepAliveConnections[i];
106                 Connection connection = holer.getConnection();
107                 holer.incrementKeepAliveCheckCount();
108 
109                 boolean validate = false;
110                 try {
111                     this.validateConnection(connection);
112                     validate = true;
113                 } catch (Throwable error) {
114                     if (LOG.isDebugEnabled()) {
115                         LOG.debug("keepAliveErr", error);
116                     }
117                     // skip
118                 }
119                 
120                 boolean discard = !validate;
121                 if (validate) {
122                     holer.lastKeepTimeMillis = System.currentTimeMillis();
123                     boolean putOk = put(holer, 0L, true);
124                     if (!putOk) {
125                         discard = true;
126                     }
127                 }
128                 //如果连接不可用,则关闭
129                 if (discard) {
130                     try {
131                         connection.close();
132                     } catch (Exception e) {
133                         // skip
134                     }
135 
136                     lock.lock();
137                     try {
138                         discardCount++;
139 
140                         if (activeCount + poolingCount <= minIdle) {
141                             emptySignal();
142                         }
143                     } finally {
144                         lock.unlock();
145                     }
146                 }
147             }
148             this.getDataSourceStat().addKeepAliveCheckCount(keepAliveCount);
149             Arrays.fill(keepAliveConnections, null);
150         }
151         //如果需要创建连接,则释放锁进行创建
152         if (needFill) {
153             lock.lock();
154             try {
155                 int fillCount = minIdle - (activeCount + poolingCount + createTaskCount);
156                 for (int i = 0; i < fillCount; ++i) {
157                     emptySignal();
158                 }
159             } finally {
160                 lock.unlock();
161             }
162         } else if (onFatalError || fatalErrorIncrement > 0) {
163             lock.lock();
164             try {
165                 emptySignal();
166             } finally {
167                 lock.unlock();
168             }
169         }
170     }

 我们到底是否要开启MySQL的PSCache,我认为应当根据站点的实际情况而定

  • MySQL5.7之前就不要开了
  • Druid有个监控后台,打开后在数据库连接一栏可以看到PSCache的命中数据。如果命中率很好,那么对站点性能是有价值的,可以尝试。如果命中率很低,则不建议开启。毕竟PSCache是占用内存的(附Druid官方文档中关于如何配置监控后台)
  • 根据阿里工程师的意见,项目中如果使用了MyBatis这样的组件,并且使用了#的值引入方式,则无需担心SQL注入问题。那么MySQL中Prepared statement的价值就不存在了。

 

posted @ 2022-12-15 19:52  Boblim  阅读(464)  评论(0编辑  收藏  举报