美文网首页
Druid连接池在mysql的场景PS Cache是否需要开启?

Druid连接池在mysql的场景PS Cache是否需要开启?

作者: 小胖学编程 | 来源:发表于2021-01-19 11:26 被阅读0次

    看一下,自己项目的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相关配置文章中:

    image.png

    指定了该参数的使用场景。

    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基本的两种模式&客户端空间占用的源码分析

    相关文章

      网友评论

          本文标题:Druid连接池在mysql的场景PS Cache是否需要开启?

          本文链接:https://www.haomeiwen.com/subject/vwdqzktx.html