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占用的平均内存)
发表评论