看一下,自己项目的druid连接池是否设置了PS Cache。是否需要设置?
1. Druid的相关配置
spring:
datasource:
name: mysql_test
type: com.alibaba.druid.pool.DruidDataSource
#druid相关配置
druid:
#打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false。
pool-prepared-statements: true
# max-pool-prepared-statement-per-connection-size: 20
# 或者(只要maxOpenPreparedStatements或者max-pool-prepared-statement-per-connection-size大于0,那么pool-prepared-statements默认true)
maxOpenPreparedStatements: 20
在github上druid的dataSource相关配置文章中:
指定了该参数的使用场景。
2. mysql下为什么不推荐使用
使用场景:oracle设为true,mysql设为false。分库分表较多推荐设置为false。
2.1 为什么要做PS Cache
当使用PrepareStatement的时候,同样的SQL语句参数不同时,我们希望客户端或服务端就只需要对SQL语句只解析一次(即只创建一次PreparedStatement对象),这样在一些大并发场景下性能更佳,尤其是互联网高并发的重复SQL场景,解析会占据较大的CPU和时间开销,而本身的执行时间可能占用并不大。
2.2 mysql下的PS Cache
druid会将PrepareStatement对象存储到Map中(LRU缓存,可以通过参数决定缓存的大小)。
存储的源码:com.alibaba.druid.pool.DruidPooledConnection#closePoolableStatement
在closeStatement
时,将PrepareStatement
对象放入缓存中。
public void closePoolableStatement(DruidPooledPreparedStatement stmt) throws SQLException {
PreparedStatement rawStatement = stmt.getRawPreparedStatement();
...
PreparedStatementHolder stmtHolder = stmt.getPreparedStatementHolder();
stmtHolder.decrementInUseCount();
if (stmt.isPooled() && holder.isPoolPreparedStatements() && stmt.exceptionCount == 0) {
//放入缓存
holder.getStatementPool().put(stmtHolder);
stmt.clearResultSet();
holder.removeTrace(stmt);
stmtHolder.setFetchRowPeak(stmt.getFetchRowPeak());
stmt.setClosed(true); // soft set close
} ...
}
查看LRU缓存对象源码:com.alibaba.druid.pool.PreparedStatementPool.LRUCache
可以发现,该缓存对象操作不能保证线程安全。
public class LRUCache extends LinkedHashMap {
private static final long serialVersionUID = 1L;
public LRUCache(int maxSize){
super(maxSize, 0.75f, true);
}
protected boolean removeEldestEntry(Entry eldest) {
boolean remove = (size() > dataSource.getMaxPoolPreparedStatementPerConnectionSize());
if (remove) {
closeRemovedStatement(eldest.getValue());
}
return remove;
}
}
druid在存储PreparedStatementHolder
时并没有保证线程安全,是因为Connection连接本身是线程安全的。
2.3 mysql以及大量分库分表不推荐使用PS Cache
缓存本质上是空间换取时间。当占用大量空间但缓存命中率低且命中若没有收益,那么就不推荐使用缓存了。
mysql的PS Cache是Connection级别的,当相同的sql被不同的Connection执行时,也不会共享彼此的Connection连接(会在新的Connection重新维护一份)。而执行sql从连接池中获取连接都是无规则的,(同一个会话中开启事务管理器除外),也就是同样的SQL在不同的连接中被使用是十分正常的事情。
即使每个元素在PS Cache占用的内存不多,但因为是Connection级别,当存在大量分库分表的场景下,会产生大量的Connection连接。累积下就会占用大量的内存。
max-pool-prepared-statement-per-connection-size
设置的过小,会导致命中率很低,基本没有什么作用。但设置的过大(例如200)会占用大量内存。而且即使命中中了,因为mysql不支持游标,效果不是很好。
综上所述:mysql的druid连接池没必要开启ps cache。
2.4 其他数据库是否开启PS Cache
此处讲述的主要是MySQL及其JDBC的PS Cache
的部分问题,在实际的场景中可以作为参考避免一些不必要的问题发生(也未必会遇得到),这些糟点在其他数据库上未必是适用的,例如Oracle在SQL Parser
有:Hard Parser
和Soft Parser
,Soft Parser
的就是语法树结果,它存放在Oracle一个单独的共享区域中,并非Session级别,所以不同的Connection之间可以共享同样的SQL语句,Oracle针对所有的SQL语句都会使用LRU算法进行Cache,单从本文提到的PS Cache
来讲在Oracle上体现的效果会更好一些。
推荐阅读
DruidDataSource一次踩坑记录
MySQL JDBC为什么都不开启PreparedStatement Cache
MySQL JDBC PrepareStatement基本的两种模式&客户端空间占用的源码分析