Druid 专题

数据源配置:

#datasource
#Introductions: https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8
#https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_DruidDataSource%E5%8F%82%E8%80%83%E9%85%8D%E7%BD%AE
#初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 default:0
spring.datasource.druid.initial-size=2
#最大连接池数量。default=8+
spring.datasource.druid.max-active=20
#最小连接池数量。maxIdle已经废弃
spring.datasource.druid.min-idle=10
#获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁
spring.datasource.druid.max-wait=60000
#是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。default=false
spring.datasource.druid.pool-prepared-statements=false
#要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,会存在Oracle下PSCache占用内存过多的问题,可以把这个数据配置大一些,比如100.default=-1
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=-1
#用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow,testOnBorrow,testOnReturn,testWhileIdle都不会起作用。这个可以不配置
#spring.datasource.druid.validation-query=select 'x'
#单位:秒,检测连接是否有效的超时时间。底层调用jdbc Statement对象的void. setQueryTImeout(int seconds)方法,mysql实现的不是很合理,不建议在mysql下配置此参数
#spring.datasource.druid.validation-query-timeout=60
#申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。default=true
spring.datasource.druid.test-on-borrow=false
#归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。default=false
spring.datasource.druid.test-on-return=false
#建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。default=false
spring.datasource.druid.test-while-idle=true
#连接池中的minIdle数据以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作。default=false
spring.datasource.druid.keep-alive=true
#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 default=1分钟
#有两个含义:
# (1)Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接
# (2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明
spring.datasource.druid.time-between-eviction-runs-millis=60000
#池中的连接保持空闲而不被驱逐的最小时间,单位是毫秒
spring.datasource.druid.min-evictable-idle-time-millis=300000
#合并多个DruidDataSource的监控数据
spring.datasource.druid.use-global-data-source-stat=true
#spring.datasource.druid.filters=#配置多个英文逗号分隔
# 配置StatFilter
spring.datasource.druid.filter.stat.enabled=true
spring.datasource.druid.filter.stat.db-type=mysql
spring.datasource.druid.filter.stat.log-slow-sql=true
spring.datasource.druid.filter.stat.slow-sql-millis=2000
spring.datasource.druid.filter.stat.merge-sql=true
# 配置WallFilter
spring.datasource.druid.filter.wall.enabled=true
spring.datasource.druid.filter.wall.db-type=mysql
spring.datasource.druid.filter.wall.config.delete-allow=false
spring.datasource.druid.filter.wall.config.drop-table-allow=false
spring.datasource.druid.filter.wall.config.create-table-allow=false
spring.datasource.druid.filter.wall.config.alter-table-allow=false
spring.datasource.druid.filter.wall.config.truncate-allow=false

 

 

 

 

http://120.26.192.168/druid/datasource.html
如果你用druid,看监控数据库,NotEmptyWaitCount数量多,或者可以考虑加大连接池的MaxActive数量。
数据库处理不过来导致execute queryTimeout了

 

有一个Druid连接池的错误,错误信息是这样的“The last packet successfully received from the server was 3,984,663 milliseconds ago. The last packet sent successfully to the server was 37 milliseconds ago.”,连接池缺省配置的testWhileIdle,按理来说不应该发生这样的错误。阿里应用一直没问题,但是外部的用户偶尔零星反馈过来,多年来都是如此,我一直很担心。

最近几天有一个阿里云的用户遇到这样的问题,直接通过阿里云客服找过来支持,经过多次在对方生产环境测试验证,终于找到原因,是用法的问题,不是Druid的问题,确认了这个之后,多年的担心可放下了。


问题是这样的,申请连接后间隔长时间再createStatememt执行Sql,而testWhileIdle只在getConnectin()时发挥作用。代码示例如下:
Connection conn = druidDataSource.getConnection();
// 这里做耗时很长的事情,比如一个Hive大任务,跑数个小时
Statement stmt = conn.createStatement();
stmt.execute(); // 这里抛错

 



 

 

Druid内置提供了四种LogFilter(Log4jFilter、Log4j2Filter、CommonsLogFilter、Slf4jLogFilter),用于输出JDBC执行的日志。这些Filter都是Filter-Chain扩展机制中的Filter,所以配置方式可以参考这里:Filter配置

1. 别名映射

在druid-xxx.jar!/META-INF/druid-filter.properties文件中描述了这四种Filter的别名

  druid.filters.log4j=com.alibaba.druid.filter.logging.Log4jFilter
  druid.filters.log4j2=com.alibaba.druid.filter.logging.Log4j2Filter
  druid.filters.slf4j=com.alibaba.druid.filter.logging.Slf4jLogFilter
  druid.filters.commonlogging=com.alibaba.druid.filter.logging.CommonsLogFilter
  druid.filters.commonLogging=com.alibaba.druid.filter.logging.CommonsLogFilter

他们的别名分别是log4j、log4j2、slf4j、commonlogging和commonLogging。其中commonlogging和commonLogging只是大小写不同。

  <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
      init-method="init" destroy-method="close">
      ... ...
      <property name="filters" value="stat,log4j" />
  </bean>

2. loggerName配置

LogFilter都是缺省使用四种不同的Logger执行输出,看实现代码:

  public abstract class LogFilter {
      protected String          dataSourceLoggerName                 = "druid.sql.DataSource";
      protected String          connectionLoggerName                 = "druid.sql.Connection";
      protected String          statementLoggerName                  = "druid.sql.Statement";
      protected String          resultSetLoggerName                  = "druid.sql.ResultSet";
  }

你可以根据你的需要修改,在log4j.properties文件上做配置时,注意配置使用相关的logger。

2. 配置输出日志

缺省输入的日志信息全面,但是内容比较多,有时候我们需要定制化配置日志输出。

<bean id="log-filter" class="com.alibaba.druid.filter.logging.Log4jFilter">
    <property name="resultSetLogEnabled" value="false" />
</bean>

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    ...
    <property name="proxyFilters">
        <list>
            <ref bean="log-filter"/>
        </list>
    </property>
</bean>
参数说明
dataSourceLogEnabled所有DataSource相关的日志
connectionLogEnabled所有连接相关的日志
connectionLogErrorEnabled所有连接上发生异常的日志
statementLogEnabled所有Statement相关的日志
statementLogErrorEnabled所有Statement发生异常的日志
resultSetLogEnabled 
resultSetLogErrorEnabled 
connectionConnectBeforeLogEnabled 
connectionConnectAfterLogEnabled 
connectionCommitAfterLogEnabled 
connectionRollbackAfterLogEnabled 
connectionCloseAfterLogEnabled 
statementCreateAfterLogEnabled 
statementPrepareAfterLogEnabled 
statementPrepareCallAfterLogEnabled 
statementExecuteAfterLogEnabled 
statementExecuteQueryAfterLogEnabled 
statementExecuteUpdateAfterLogEnabled 
statementExecuteBatchAfterLogEnabled 
statementCloseAfterLogEnabled 
statementParameterSetLogEnabled 
resultSetNextAfterLogEnabled 
resultSetOpenAfterLogEnabled 
resultSetCloseAfterLogEnabled 

4. log4j.properties配置

如果你使用log4j,可以通过log4j.properties文件配置日志输出选项,例如:

  log4j.logger.druid.sql=warn,stdout
  log4j.logger.druid.sql.DataSource=warn,stdout
  log4j.logger.druid.sql.Connection=warn,stdout
  log4j.logger.druid.sql.Statement=warn,stdout
  log4j.logger.druid.sql.ResultSet=warn,stdout

5. 输出可执行的SQL

Java启动参数配置方式

  -Ddruid.log.stmt.executableSql=true

logFilter参数直接配置

  <bean id="log-filter" class="com.alibaba.druid.filter.logging.Log4jFilter">
        <property name="statementExecutableSqlLogEnable" value="true" />
  </bean>

https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_LogFilter


MySql+Mybatis+Druid:sql injection violation, multi-statement not allow

做一个批量update的操作 ,sqlmap如下:

 

[html]  view plain  copy
 
  1. <update id="updateBatch" parameterType="java.util.List">  
  2.         <foreach collection="list" item="item" index="index" open="" close="" separator=";">  
  3.                 update device_bd_token   
  4.                 <set>  
  5.                 access_token=#{item.accessToken}  
  6.                 </set>  
  7.                 where device_id = #{item.deviceId}  
  8.          </foreach>  
  9.     </update>  


结果报错:

 

[java]  view plain  copy
 
  1. Caused by: java.sql.SQLException: sql injection violation, multi-statement not allow : update device_bd_token   
  2.                  SET access_token=?   
  3.                 where device_id = ?  
  4.           ;   
  5.                 update device_bd_token   
  6.                  SET access_token=?   
  7.                 where device_id = ?  
  8.     at com.alibaba.druid.wall.WallFilter.check(WallFilter.java:714)  
  9.     at com.alibaba.druid.wall.WallFilter.connection_prepareStatement(WallFilter.java:240)  
  10.     at com.alibaba.druid.filter.FilterChainImpl.connection_prepareStatement(FilterChainImpl.java:448)  
  11.     at com.alibaba.druid.filter.FilterAdapter.connection_prepareStatement(FilterAdapter.java:928)  
  12.     at com.alibaba.druid.filter.FilterEventAdapter.connection_prepareStatement(FilterEventAdapter.java:122)  
  13.     at com.alibaba.druid.filter.FilterChainImpl.connection_prepareStatement(FilterChainImpl.java:448)  
  14.     at com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl.prepareStatement(ConnectionProxyImpl.java:342)  
  15.     at com.alibaba.druid.pool.DruidPooledConnection.prepareStatement(DruidPooledConnection.java:318)  


刚开始以为是连接数据库的url上没有加上支持批量的参数,然后就改了下:

 

[java]  view plain  copy
 
  1. jdbc.url=jdbc:mysql://192.168.11.107:3306/alarm_db?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8  


结果还是同样的错误!但是在命令行直接执行又是没问题的,这就很奇怪了!

 

仔细看日志,好像是Druid的WallFilter.check()抛出来的,那就是说是Druid在做预编译的时候,给抛出的异常,还没有到mysql的服务器。

 

最终的解决办法是这样的:

 

[html]  view plain  copy
 
  1. <bean id="dataSourceOne" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">  
  2.         <property name="proxyFilters">  
  3.             <list>  
  4.                 <ref bean="stat-filter" />  
  5.                 <ref bean="wall-filter"/>  
  6.             </list>  
  7.         </property>  
  8.     </bean>  

 

[html]  view plain  copy
 
  1. <bean id="wall-filter" class="com.alibaba.druid.wall.WallFilter">  
  2.         <property name="config" ref="wall-config" />  
  3.     </bean>  
  4.       
  5.     <bean id="wall-config" class="com.alibaba.druid.wall.WallConfig">  
  6.         <property name="multiStatementAllow" value="true" />  
  7.     </bean>  


配置一个multiStatementAllow参数就可以了。

 

看下源码的处理:

 

也就是说,只要把config的multiStatementAllow设置为true就可以避免出现这样的错误了!

Druid配置的时候还有一个大坑就是,不要同时配置filters和proxyFilters,filter都是内置的,想通过proxyFilters来定制的话,就不要配置filters。

 

DruidDataSource继承了DruidAbstractDataSource,

可以看出来,既可以配置filters,也可以配置proxyFilters,不同的是,filters是字符串别名,proxyFilters是类。

我们继续看一下这些字符串的值应该是啥样的:

 

原来在这里:

这就是druid内置的所有的filter了,去掉前缀druid.filters就是别名了。




 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值