数据库性能最佳实践
当应用需要连接数据库时,那么应用的性能就可能收到数据库性能的影响。比如当数据库的I/O能力存在限制,或者因缺失了索引而导致执行的SQL语句需要对整张表进行遍历。对于这些问题,仅仅对应用代码进行优化可能是不够,还需要了解数据库的知识和特点。
示例数据库
该数据库表示了128只股票在1年内(261个工作日)的股价信息。
其中有两张表:STOCKPRICE和STOCKOPTIONPRICE。 STOCKPRICE中使用股票代码作为主键,另外还有日期字段。它有33408条记录(128 * 261)。 STOCKOPTIONPRICE中存放了每只股票在每天的5个Options,主键是股票代码,另外还有日期字段和表示Option号码的一个整型字段。它有167040条记录(128 * 261 * 5)。
JPA
对JPA的性能影响最大的是它使用的JDBC Driver。除此之外,还有一些其他因素也会影响JPA的性能。
JPA是通过对实体类型的字节码进行增强来提高JPA的性能的,这一点在Java EE环境中对用户是透明的。但是在Java SE环境中需要确保这些字节码操作的正确性。否则,会出现各种各样的问题影响JPA的性能,比如:
- 需要懒加载(Lazy Load)的字段被立即加载(Eager Load)了
- 保存到数据库中的字段出现了不必要的冗余
- 应当保存到JPA缓存中的数据没有保存,导致本不必要的重取(Refetch)操作
JPA对于字节码的增强一般作为编译阶段的一部分。在实体类型被编译成为字节码后,它们会被后置处理程序(它们是实现相关的,也就是EclipseLink和Hibernate使用的后置处理程序是不同的)进行处理来增强这些字节码,得到经过优化了的字节码文件。
在有的JPA实现中,还提供了当类被加载到JVM中时,动态增强字节码的方法。需要为JVM指定一个agent,通过启动参数的形式提供。比如当希望使用EclipseLink的这一功能时,可以传入:-javaagent:path_to/eclipselink.jar
事务处理(Transaction Handling)
JPA可以使用在Java SE和Java EE应用中。区别在于事务的处理方式。
在Java EE中,JPA事务只是应用服务器的Java事务API(JTA)实现的一部分。它提供了两种方式用来处理事务的边界:
- 容器管理事务(Container-Managed Transaction,CMT)
- 用户管理事务(User-Managed Transaction, UMT)
顾名思义,CMT会将事务的边界处理委托给容器,而UMT则需要用户在应用中指定边界的处理方式。在合理使用的情况下,CMT和UMT并没有显著的区别。但是,在使用不当的情况下,性能就会出现差异了,尤其是在使用UMT时,事务的范围可能会定义的过大或者过小,这样会对性能造成较大的影响。可以这样理解:CMT提供了一种通用的和折中的事务边界处理方式,使用它通常会更安全,而UMT则提供了一种更加灵活的处理方式,但是灵活是建立在用户必须十分了解它的基础上的。
@Stateless
public class Calculator {
@PersistenceContext(unitName="Calc")
EntityManager em;
@TransactionAttribute(REQUIRED)
public void calculate() {
Parameters p = em.find(...);
// ...perform expensive calculation...
em.persist(...answer...);
}
}
上述代码使用了CMT(使用了@TransactionAttribute注解),事务的作用域是整个方法。当隔离等级是可重复读(Repeatable Read)时,意味着在进行计算(以上的Expensive Calculation注释行)时,需要的数据会一直被锁定,从而对性能造成了影响。
在使用UMT时,会更灵活一点:
@Stateless
public class Calculator {
@PersistenceContext(unitName="Calc")
EntityManager em;
public void calculate() {
UserTransaction ut = ... lookup UT in application server...;
ut.begin();
Parameters p = em.