Druid連接池在mysql的場景PS Cache是否需要開啓? 1. Druid的相關配置 2. mysql下爲什麼不推薦使用 推薦閱讀

看一下,自己項目的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

緩存本質上是空間換取時間。當佔用大量空間但緩存命中率低且命中若沒有收益,那麼就不推薦使用緩存了。

  1. mysql的PS Cache是Connection級別的,當相同的sql被不同的Connection執行時,也不會共享彼此的Connection連接(會在新的Connection重新維護一份)。而執行sql從連接池中獲取連接都是無規則的,(同一個會話中開啓事務管理器除外),也就是同樣的SQL在不同的連接中被使用是十分正常的事情。

  2. 即使每個元素在PS Cache佔用的內存不多,但因爲是Connection級別,當存在大量分庫分表的場景下,會產生大量的Connection連接。累積下就會佔用大量的內存。

  3. 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 ParserSoft ParserSoft Parser的就是語法樹結果,它存放在Oracle一個單獨的共享區域中,並非Session級別,所以不同的Connection之間可以共享同樣的SQL語句,Oracle針對所有的SQL語句都會使用LRU算法進行Cache,單從本文提到的PS Cache來講在Oracle上體現的效果會更好一些。

推薦閱讀

DruidDataSource一次踩坑記錄

MySQL JDBC爲什麼都不開啓PreparedStatement Cache

MySQL JDBC PrepareStatement基本的兩種模式&客戶端空間佔用的源碼分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章