高速缓存和连接池对数据库性能的影响
内容:
初识高速缓存和连接池
Cache(高速缓存)和Connection Pool(连接池)的概念和机制
高速缓存的参数设定
连接池的设置和应用
性能问题的深远思考
解决性能问题的几条经验
初识高速缓存和连接池
设想这样一种情形:你忽然口渴,需要一杯水来缓解,从心情上来讲,当然是越快越好了。通常,一杯水的产生包括从水源(井水、河水或江水、甚至海水等)抽取,通过管道传输和设备净化,才到达你饮水的容器中。上述过程是必须的,但并不是每一杯水的产生都必须把上述过程重复一次。你可以用一个大一点的容器(例如缸或罐等)来盛大量的水,喝水之前分到杯子小部分中即可,你的代价只是把水从缸转移到杯子;你还可以在大量用水(例如洗澡或洗衣服等)时,只需打开水阀,而不必临时铺设通往水源的管道和购买净化水的设备。因为水是人们生活不可缺少的东西,每时每刻都在被大量地使用,而且物理本质也完全相同,所以政府会铺设管道和建设水处理站,完成那些比较困难的工作,达到资源共享的目的,而你也可针对自己的需求,用容器来盛那些具有特定用途的水。本文将要和你讨论的高速缓存和连接池与上述特定容器和传输管道有很多相似之处,它们都达到了同一个目的:在满足用户意愿的前提下,尽可能地共享资源,以提高整个系统的性能。
高速缓存和连接池是数据访问中的重要技术,某些情况下的应用对访问数据库的性能有巨大的提高,而且都得到了数据库业界的普遍支持。前者由DBMS厂商针对自己的数据库实现,提供可供用户配置的方案;后者是JDBC的一个标准接口,由支持J2EE技术的应用服务器厂商提供具体的实现,而你的Java程序代码无需更改。本文将向你简单介绍高速缓存和连接池的概念和机制,并以PointBase数据库为例向你展示高速缓存的应用,而一个简单的连接池应用场景将向你描述应用的条件和提高的性能。
Cache(高速缓存)和Connection Pool(连接池)的概念和机制
它们不是数据库独有的技术,但却得到数据库业界的普遍支持,并在其它数据存取和对象复用领域有很多类似的应用。
Cache(高速缓存)
作为个人计算机的日常使用者,你肯定听说过这些名词:Cache(高速缓存)、Memory(内存)、Hard disk(硬盘)。它们都是数据存取单元,但存取速度却有很大差异,呈依次递减的顺序。对于CPU来说,它可以从距离自己最近的Cache高速地存取数据,而不是从内存和硬盘以低几个数量级的速度来存取数据。而Cache中所存储的数据,往往是CPU要反复存取的数据,有特定的机制(或程序)来保证Cache内数据的命中率(Hit Rate)。因此,CPU存取数据的速度在应用高速缓存后得到了巨大的提高。
对于数据库来说,厂商的做法往往是在内存中开辟相应的区域来存储可能被多次存取的数据和可能被多次执行的语句,以使这些数据在下次被访问时不必再次提交对DBMS的请求和那些语句在下次执行时不必再次编译。
因为将数据写入高速缓存的任务由Cache Manager负责,所以对用户来说高速缓存的内容肯定是只读的。需要你做的工作很少,程序中的SQL语句和直接访问DBMS时没有分别,返回的结果也看不出有什么差别。而数据库厂商往往会在DB Server的配置文件中提供与Cache相关的参数,通过修改它们,可针对我们的应用优化Cache的治理。下图是在Win2K中配置MS Access数据源的界面,在"驱动程序"部分你可设置的页超时和缓冲区大小就是和Cache有关的参数。在后面的讨论中,我将展示一个更复杂的数据库,向你解释Cache对访问数据库性能的影响和如何寻找最优的配置方案。
Connection Pool(连接池)
池是一个很普遍的概念,和缓冲存储有机制相近的地方,都是缩减了访问的环节,但它更注重于资源的共享。下图展示了建立"调制解调器池"以共享调制解调器资源的VPN拨号方案:
对于访问数据库来说,建立连接的代价比较昂贵,因此,我们有必要建立"连接池"以提高访问的性能。我们可以把连接当作对象或者设备,池中又有许多已经建立的连接,访问本来需要与数据库的连接的地方,都改为和池相连,池临时分配连接供访问使用,结果返回后,访问将连接交还。
JDBC 1.0标准及其扩展中没有定义连接池,而在JDBC 2.0标准的扩展中定义了与连接池相关的接口。与接口对应的类由应用服务器厂商实现,你可在对服务器的治理过程中调节某个数据库连接池的参数。下图简略地描述了连接池的运行机制:
高速缓存的参数设定
在PointBase数据库DB Server的配置参数列表中,我们可以找到这几个参数:cache.checkpointinterval、cache.size、SQLCaching.size等。下表是对各个参数的描述:
参数名 参数描述
cache.checkpointinterval 检查点的时间间隔
cache.size 高速缓存的最大页数(素数时,性能最好)
SQLCaching.size 高速缓存中SQL语句的个数
对于cache.checkpointinterval来说,和前面Access界面中的页超时是一个概念,它指定了页面内容更新的时间间隔,这取决于你的应用对时效性的要求程度。
对于cache.size来说,指定了页面的个数,一般应设定为符合你查询结果的需求。至于为何是素数,我也纳闷,不过还是遵照厂商的指示吧。
对于SQLCaching.size来说,指定了存储的经过编译的SQL语句的个数,你可以把它设定为0,从而取消这个选项。
使用Cache后,性能到底有多大提高?我打开PointBase的Console通过JDBC驱动访问Server,将SQL菜单下的Timing Mode选上,以显示各个步骤的耗费时间。执行语句是:
SELECT * FROM PRODUCT_TBL
第一次访问,总计耗时1082毫秒,而编译耗时771毫秒。
紧接着的第二次访问,总计耗时仅为160毫秒,而编译耗时为0。
再接着的第三次访问,总计耗时仅为91毫秒,编译耗时也为0。
关闭Console,等待超过30秒之后,重新开启Console,执行相同的语句,总计耗时210毫秒,编译耗时为20毫秒。
自等待超过30秒之后,执行语句,总计耗时101毫秒,编译耗时为0。
由此可以看出,高速缓存的应用大大提高了访问数据库的性能,而其参数的设定则要依据前面对它们的描述来进行,需要你仔细阅读数据库的配置文档。
连接池的设置和应用
我选择IBM公司的应用服务器平台WebSphere来给大家演示连接池的设置,使你面对友好的Web界面,可以体验到非常简易的操作场景。
首先,我们进入WebSphere的治理控制台,这是一个非常漂亮的Web界面:
紧接着,我选定一个数据源:Session Persistence datasource,就可看到这个数据源的属性配置了。在这儿,仅仅列举和连接池有关的属性:
Minimum Pool Size 池中保持的连接的最小数目;有新的请求,且没有激活连接可供使用时,池中连接数将增大,到最大连接数为止
Maximum Pool Size 池中保持的连接的最大数目;当这个数目达到,且没有激活连接可供使用时,新的请求将等待
Connection Timeout 当连接数达到最大值,且激活连接都在被使用时,新的请求等待的时间
Idle Timeout 连接可在池中闲置的时间;超过将释放资源,到最小连接数为止
Orphan Timeout 连接在被应用控制时,可闲置的时间;超过将返回池中
你可以根据需要来修改这些数值,以满足你的应用需要。接下来,我们讨论一下连接池的应用。
EJB访问数据库(场景1,使用JDBC 1.0) import java.sql.*;
import javax.sql.*;
...
public class AccountBean implements EntityBean {
...
public Collection ejbFindByLastName(String lName) {
try {
String dbdriver = new initialContext().lookup("java:comp/env/DBDRIVER").toString();
Class.forName(dbdriver).newInstance();
Connection conn = null;
conn = DriverManager.getConnection("java:comp/env/DBURL", "userID", "passWord");
...
conn.close();
}
...
}
假如EntityBean是一个共享组件,那么每次客户请求时,都要建立和释放与数据库的连接,这成为影响性能的主要问题。
EJB访问数据库(场景2,使用JDBC 2.0) import java.sql.*;
import javax.sql.*;
// import here vendor specific JDBC drivers
public ProductPK ejbCreate() {
try {
// initialize JNDI lookup parameters
Context ctx = new InitialContext(parms);
...
ConnectionPoolDataSource cpds = (ConnectionPoolDataSource)ctx.lookup(cpsource);
...
// Following parms could all come from a JNDI look-up
cpds.setDatabaseName("PTDB");
cpds.setUserIF("XYZ");
...
PooledConnection pc = cpds.getPooledConnection();
Connection conn = pc.getConnection();
...
// do business logic
conn.close();
}
...
}
EJB组件利用JNDI的lookup()方法定位数据库的连接池资源,利用getConnection()方法得到已经打开的数据库连接,而用close()来释放连接,放回池中。因此,与场景1相比,少了与数据库建立物理连接的损耗。对于原本要频繁打开和关闭物理连接的应用来说,通过这种建立逻辑连接并复用的方法,性能肯定能够得到大幅度提高。
性能问题的深远思考
性能问题并不局限于数据库的应用上,而是存在于每个软件系统中。我们希望软件系统付出的最小,而获得的最大,因而无时无刻不在优化它们。通过《Java程序访问数据库的速度瓶颈问题的分析和解决》和本文,我对Java程序访问数据库的性能问题做了分析,并提供了优化Java程序的解决方案,希望对你有所帮助。
也许你会关心碰到类似的性能问题时应如何分析和解决,我就针对此次探讨"访问数据库的速度瓶颈"问题的过程中碰到的难题、网友的意见和自己的体会,作一个关于"方法论"的经验总结,希望能够抛砖引玉,更好地解决类似的问题。
解决性能问题的几条经验
不要让硬件的低配置成为软件正常运行的障碍,后者有升级前者的需求,请立即满足;经常碰到这样的问题"P166+64M的机子跑Win2K+mysql+JBoss,能跑么?"我在回答"可以"的同时只有对着屏幕发呆了。
尽量使用商业软件,并享受良好的售后技术支持;假如你没有黑客精神,请不要使用自由软件。
分析好自己的问题,也许它的本质和他人的不同;没有一把钥匙可打开的任一把锁。
确定瓶颈环节的位置;解决了瓶颈问题,往往整个的性能问题就解决了,千万不要抓着边缘的问题不放。
将瓶颈环节细分为多个顺序的流程,用逐个替代的方法来试探瓶颈的核心位置;细分问题使你都问题有更进一步的了解。
做自己能做的和该做的事情,始终面向自己的现实问题;不要尝试那些应该由厂商解决的问题(例如,自己写个JDBC驱动)。
他人的方案只供自己参考,解决要靠自己思考;你我应用的情形不同,应用解决方案要在理解他人的方案之后。
观察新技术,应用新技术;它往往包含了前人对问题解决的思路,只是对你来说不可见。
问题得到解决后,立即罢手,并汇报结果;在现实问题得到解决后,没有必要花费精力在非核心的问题上,也许它们永远不会被碰到,不要假想问题让自己解决。
上述经验为个人即兴的总结,并未有严谨的逻辑推导,仅代表我解决技术问题的思路,供你碰到类似问题时参考。