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