首页java › JBOSS连接池PreparedStatementCache参数的作用及原理

JBOSS连接池PreparedStatementCache参数的作用及原理

BOSS连接池配置文件中有个参数,叫做PreparedStatementCache,这个值怎么设置呢,对于某些数据库来讲,这个值的设置会产生比较大的性能影响。

先来看两个使用JAVA语言来查询数据库例子:

例1:

String sql = "select * from table_name where id = ";
Statement stmt = conn.createStatement();;
rset = stmt.executeQuery(sql+"1");;

例2:

String v_id = 'xxxxx';
String v_sql = 'select name from table_a where id = ? ';	 //嵌入绑定变量
stmt = con.prepareStatement(v_sql);
stmt.setString(1, v_id ); //为绑定变量赋值
stmt.executeQuery();

例1和例2有什么区别呢?
先来看看JBOSS源码中对于这两个方法的解释:

createStatement()方法
创建一个Statement对象,用于发送SQL语句到数据库。没有使用绑定变量的SQL语句一般使用Statement来执行。如果一个相同的SQL语句被执行多次,则使用PreparedStatement是一个更好的方式。

PreparedStatement prepareStatement(String sql)方法
创建一个PreparedStatement对象,用于发送使用绑定变量的SQL语句到数据库中。SQL语句能够被预编译,并且存储到一个PreparedStatement对象中。这个对象,可以在多次执行这个SQL语句的块景中被高效的使用(使用绑定变量的SQL至少可以省去数据库的硬解析)。
因为SQL可以被预编译,所以这种方式用于使用绑定变量的SQL。如果驱动程序支持绑定变量,方法prepareStatement将会发送一个SQL语句到数据库,以执行预编译(即数据库中的解析)。也有一些驱动不支持预编译,在这种情况下,在PreparedStatement执行之后,语句才会被发送到数据库,这对于用户没有直接的作用。

这是源码中对于这两个方法的解释。

简单来说,这是数据库执行SQL的两种方式。第一种方式,直接发送SQL语句到数据库执行。第二种方式在使用上较第一个方式会复杂一点,首先发送SQL到数据库,进行解析,然后将有关这个SQL的信息存储到一个PreparedStatement,这个PreparedStatement可以被同样的SQL语句反复使用。

PreparedStatement是JDBC里面提供的对象,而JBOSS里面引入了一个PreparedStatementCache,PreparedStatementCache即用于保存与数据库交互的prepareStatement对象。PreparedStatementCache使用了一个本地缓存的LRU链表来减少SQL的预编译,减少SQL的预编译,意味着可以减少一次网络的交互和数据库的解析(有可能在session cursor cache hits中命中,也可能是share pool中命中),这对应用的DAO响应延时是很大的提升。

下面是JBOSS源代码中对于prepareStatementCache的实现。

BaseWrapperManagedConnection类是JBOSS连接管理最基本的一个抽象类,有两个类继续自这个抽象类,分别为:
1.LocalManagedConnection 本地事务的连接管理。(一般我们都使用这个类)
2.XAManagedConnection 分布式事务的连接管理

BaseWrapperManagedConnection类的构造函数中,有PreparedStatementCache的初始化。(即在连接被创建的时候,即初始化PreparedStatementCache),初始化代码如下:

      if (psCacheSize > 0)
         psCache = new PreparedStatementCache(psCacheSize);

而PreparedStatementCache这个类继承自LRUCachePolicy类,如下:

public class PreparedStatementCache extends LRUCachePolicy
   public PreparedStatementCache(int max)
   {
       //max值即为JBOSS连接池配置文件定义的psCacheSize
      super(2, max);
      create();
   }

  其中super如下:
  //根据指定的最小值和最大值,创建LRU cache管理方案。
  public LRUCachePolicy(int min, int max)
   {
      if (min < 2 || min > max)
      {throw new IllegalArgumentException("Illegal cache capacities");}
      m_minCapacity = min;
      m_maxCapacity = max;
   }

 public void create()
   {
      m_map = new HashMap();
      m_list = createList();
      m_list.m_maxCapacity = m_maxCapacity;
      m_list.m_minCapacity = m_minCapacity;
      m_list.m_capacity = m_maxCapacity;
   }

PreparedStatementCache对象其实是一个是一个LRU List,即它使用了一个双向链表来存储PreparedStatement的值,这个LRU链表的数据结构如下:

 public class LRUList
   {
      /** The maximum capacity of the cache list */
      public int m_maxCapacity;
      /** The minimum capacity of the cache list */
      public int m_minCapacity;
      /** The current capacity of the cache list */
      public int m_capacity;
      /** The number of cached objects */
      public int m_count;
      /** The head of the double linked list */
      public LRUCacheEntry m_head;
      /** The tail of the double linked list */
      public LRUCacheEntry m_tail;
      /** The cache misses happened */
      public int m_cacheMiss;
      /**
       * Creates a new double queued list.
       */
      protected LRUList()
      {
         m_head = null;
         m_tail = null;
         m_count = 0;
      }

在BaseWrapperManagedConnection被实例化的时候,即连接创建的时候,会初始化一个最小值为2,最大值为max的一个LRU双向链表(max值在jboss连接池中通过< prepared-statement-cache-size> 50< /prepared-statement-cache-size>参数来设置)。

BaseWrapperManagedConnection方法的prepareStatement方法,源代码如下:

   PreparedStatement prepareStatement(String sql, int resultSetType,
            int resultSetConcurrency) throws SQLException
   {
      if (psCache != null)
      {
      	//实例化KEY类。
         PreparedStatementCache.Key key = new PreparedStatementCache.Key(sql,
               PreparedStatementCache.Key.PREPARED_STATEMENT,
               resultSetType, resultSetConcurrency);
         /*
         根据KEY从LRU链表中获取相应的SQL,即CachedPreparedStatement,
         它包SQL语句及一些其它的环境参数。
         在get key时进行判断,若有值,则将这个KEY对象移动到LRU链表头,LRU链表的热头.
         */
         CachedPreparedStatement cachedps =
                  (CachedPreparedStatement) psCache.get(key);
         if (cachedps != null)
         {
         /*判断是否可以使用。如果没有其它人使用,则可以使用这个KEY。
         否则进行下一步判断:是否自动提交模式,是否共享cached prepared statements。
         */
            if (canUse(cachedps))
               cachedps.inUse();
            else
            	/*
            	如果不能使用,则临时创建一个PreparedStatement对象,
            	这个PreparedStatement不会被插入到psCache。
            	*/
               return doPrepareStatement(sql, resultSetType, resultSetConcurrency);
         }
         else
         {
         		//若没有找到相应的KEY, 创建一个PreparedStatement对象。
            PreparedStatement ps =
                  doPrepareStatement(sql, resultSetType, resultSetConcurrency);
            //再创建一个CachedPreparedStatement
            cachedps = wrappedConnectionFactory.createCachedPreparedStatement(ps);
            /*
            把新的SQL语句插入到psCache中。这个新的SQL语句被放到LRU链表的头部,
            同时,如果LRU链表在放置时满了,则清理最后的一个SQL。
            */
            psCache.insert(key, cachedps);
         }
         return cachedps;
      }
      /*
      如果psCache为null,则说明没用启用psCache,或者psCache为空,
      则直接创建一个PreparedStatement对象进行查询。即不使用psCache。
      */
      else
         return doPrepareStatement(sql, resultSetType, resultSetConcurrency);
   }

BaseWrapperManagedConnection是一个连接管理的抽象类,对于每一个数据库的连接,都有独立的PreparedStatementCache。
在ORACLE数据库中,使用PreparedStatementCache能够显著的提高系统的性能,前提是SQL都使用了绑定变量,因为对于ORACLE数据库而言,在PreparedStatementCache中存在的SQL,不需要open cursor,可以减少一次网络的交互,并能够绕过数据库的解析,即所有的SQL语句,不需要解析即可以直接执行。但是PreparedStatementCache中的PreparedStatement对象会一直存在,并占用较多的内存。
在MYSQL数据库中,因为没有绑定变量这个概念,MYSQL本身在执行所有的SQL之前,都需要进行解析,因此在MYSQL中这个值对性能没有明显的影响,这个值可以不设置,但是我觉得设置了这个值,可以避免在客户端频繁的创建PreparedStatement,并且对于相同的SQL,可以避免从服务器频繁的获取metadata,也有一定的好处(这是我所知道益处,总的来说,意义并没有ORACLE那么大)。
另外,PreparedStatementCache也不是设置越大越好,毕竟,PreparedStatementCache是会占用JVM内存的。之前出现过一个核心系统因为在增加了连接数(拆分数据源)后,这个参数设置没有修改,导致JVM内存被撑爆的情况。(JVM内存占用情况=连接总数*PreparedStatementCache设置大小*每个PreparedStatement占用的平均内存)

发表评论

注意 - 你可以用以下 HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>