看一下,自己項目的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<PreparedStatementKey, PreparedStatementHolder> {
private static final long serialVersionUID = 1L;
public LRUCache(int maxSize){
super(maxSize, 0.75f, true);
}
protected boolean removeEldestEntry(Entry<PreparedStatementKey, PreparedStatementHolder> 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上體現的效果會更好一些。