数据源配置:
#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如下:
- <update id="updateBatch" parameterType="java.util.List">
- <foreach collection="list" item="item" index="index" open="" close="" separator=";">
- update device_bd_token
- <set>
- access_token=#{item.accessToken}
- </set>
- where device_id = #{item.deviceId}
- </foreach>
- </update>
结果报错:
- Caused by: java.sql.SQLException: sql injection violation, multi-statement not allow : update device_bd_token
- SET access_token=?
- where device_id = ?
- ;
- update device_bd_token
- SET access_token=?
- where device_id = ?
- at com.alibaba.druid.wall.WallFilter.check(WallFilter.java:714)
- at com.alibaba.druid.wall.WallFilter.connection_prepareStatement(WallFilter.java:240)
- at com.alibaba.druid.filter.FilterChainImpl.connection_prepareStatement(FilterChainImpl.java:448)
- at com.alibaba.druid.filter.FilterAdapter.connection_prepareStatement(FilterAdapter.java:928)
- at com.alibaba.druid.filter.FilterEventAdapter.connection_prepareStatement(FilterEventAdapter.java:122)
- at com.alibaba.druid.filter.FilterChainImpl.connection_prepareStatement(FilterChainImpl.java:448)
- at com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl.prepareStatement(ConnectionProxyImpl.java:342)
- at com.alibaba.druid.pool.DruidPooledConnection.prepareStatement(DruidPooledConnection.java:318)
刚开始以为是连接数据库的url上没有加上支持批量的参数,然后就改了下:
- jdbc.url=jdbc:mysql://192.168.11.107:3306/alarm_db?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8
结果还是同样的错误!但是在命令行直接执行又是没问题的,这就很奇怪了!
仔细看日志,好像是Druid的WallFilter.check()抛出来的,那就是说是Druid在做预编译的时候,给抛出的异常,还没有到mysql的服务器。
最终的解决办法是这样的:
- <bean id="dataSourceOne" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
- <property name="proxyFilters">
- <list>
- <ref bean="stat-filter" />
- <ref bean="wall-filter"/>
- </list>
- </property>
- </bean>
- <bean id="wall-filter" class="com.alibaba.druid.wall.WallFilter">
- <property name="config" ref="wall-config" />
- </bean>
- <bean id="wall-config" class="com.alibaba.druid.wall.WallConfig">
- <property name="multiStatementAllow" value="true" />
- </bean>
配置一个multiStatementAllow参数就可以了。
看下源码的处理:
也就是说,只要把config的multiStatementAllow设置为true就可以避免出现这样的错误了!
Druid配置的时候还有一个大坑就是,不要同时配置filters和proxyFilters,filter都是内置的,想通过proxyFilters来定制的话,就不要配置filters。
DruidDataSource继承了DruidAbstractDataSource,
可以看出来,既可以配置filters,也可以配置proxyFilters,不同的是,filters是字符串别名,proxyFilters是类。
我们继续看一下这些字符串的值应该是啥样的:
原来在这里:
这就是druid内置的所有的filter了,去掉前缀druid.filters就是别名了。