数据库性能最佳实践

当应用须要连接数据库时。那么应用的性能就可能收到数据库性能的影响。

比方当数据库的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事务仅仅是应用server的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.find(...);

ut.commit();

// ...perform expensive calculation...

ut.begin();

em.persist(...answer...);

ut.commit();

}

}

上述代码的calculate方法没有使用@TransactionAttribute注解。

而是在方法中声明了两段Transaction,将昂贵的计算过程放在了事务外。当然,也能够使用CMT结合3个方法来完毕上面的逻辑。可是显然UMT更加方便和灵活。

在Java SE环境中。EntityManager被用来提供事务对象,可是事务的边界仍然须要在程序中进行设划分(Demarcating)。比方在以下的样例中:

在使用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.find(...);

ut.commit();

// ...perform expensive calculation...

ut.begin();

em.persist(...answer...);

ut.commit();

}

}

上述代码的calculate方法没有使用@TransactionAttribute注解。

而是在方法中声明了两段Transaction,将昂贵的计算过程放在了事务外。

当然,也能够使用CMT结合3个方法来完毕上面的逻辑。可是显然UMT更加方便和灵活。

在Java SE环境中。EntityManager被用来提供事务对象。可是事务的边界仍然须要在程序中进行设划分(Demarcating)。比方在以下的样例中:

public void run() {

for (int i = startStock; i < numStocks; i++) {

EntityManager em = emf.createEntityManager();

EntityTransaction txn = em.getTransaction();

txn.begin();

while (!curDate.after(endDate)) {

StockPrice sp = createRandomStock(curDate);

if (sp != null) {

em.persist(sp);

for (int j = 0; j < 5; j++) {

StockOptionPriceImpl sop = createRandomOption(sp.getSymbol, sp.getDate());

em.persist(sop);

}

}

curDate.setTime(curDate.getTime() + msPerDay);

}

txn.commit();

em.close();

}

}

上述代码中。整个while循环被包括在了事务中。和在JDBC中使用事务时一样,在事务的范围和事务的提交频度上总会做出一些权衡,在下一节中会给出一些数据作为參考。

总结

在了解UMT的前提下,使用UMT进行事务的显式管理会有更好的性能。

希望使用CMT进行事务管理时,能够通过将方法划分为多个方法从而将事务的范围变小。

JPA写优化

在JDBC中。有两个关键的性能优化方法:

重用PreparedStatement对象

使用批量更新操作

JPA也可以完毕这两种优化,可是这些优化不是通过直接调用JPA的API来完毕的,在不同的JPA实现中启用它们的方式也不尽同样。对于Java SE应用。想启用这些优化通常须要在persistence.xml文件里设置一些特定的属性。

比方,在JPA的參考实现(Reference Implementation)EclipseLink中,重用PreparedStatement须要向persistence.xml中加入一个属性:

当然,假设JDBC Driver可以提供一个Statement Pool,那么启用该特性比启用JPA的以上特性更好。毕竟JPA也是建立在JDBC Driver之上的。

假设须要使用批量更新这一优化,能够向persistence.xml中加入属性:

批量更新的Size不仅能够通过上面的eclipselink.jdbc.batch-writing.size进行设置,还能够通过调用EntityManager上的flush方法来让当前全部的Statements马上被运行。

下表显示了在使用不同的优化选项时。运行时间的不同:

优化选项

时间

无批量更新, 无Statement缓存

240s

无批量更新, 有Statement缓存

200s

有批量更新, 无Statement缓存

23.37s

有批量更新, 有Statement缓存

21.08s

总结

JPA应用和JDBC应用类似。限制对数据库写操作的次数可以提高性能。

Statement缓存可以在JPA或者JDBC层实现,假设JDBC Driver提供了这个功能,优先在JDBC层实现。

JPA更新操作有两种方式实现,一是通过声明式(即向persistence.xml加入属性),二是通过调用flush方法。

JPA读优化

由于JPA缓存的參与。使得JPA的读操作比想象中的要复杂一点。同一时候也由于JPA会将缓存的因素考虑进来,JPA生成的SQL也并非最优的。

JPA的读操作会在三个场合下发生:

调用EntityManager的find方法

运行JPA查询语句

须要使用某个实体对象关联的其他实体对象

对于前两种情况。可以控制的是读取实体对象相应表的部分列还是整行,是否读取该实体对象关联的其他对象。

尽量少地读取数据

能够将某个域设置为懒载入来避免在读该对象时就将此域同一时候读取。当读取一个实体对象时。被声明为懒载入的域将会从被生成的SQL语句中排除。此后仅仅要在调用该域的getter方法时,才会促使JPA进行一次读取操作。对于基本类型,非常少使用这个懒载入,由于它们的数据量较小。

可是对于BLOB或者CLOB类型的对象,就有必要了:

@Lob

@Column(name = "IMAGEDATA")

@Basic(fetch = FetchType.LAZY)

private byte[] imageData

以上的IMAGEDATA字段由于太大且不会常常被使用。所以被设置成懒载入。这样做的优点是:

让SQL运行的更快

节省了内存,减小了GC的压力

另外须要注意的是,懒载入的注解(fetch = FetchType.LAZY)对于JPA的实现仅仅是一个提示(Hint)。真正在运行读取操作的时候,JPA或许会忽略它。

与懒载入相反,还能够指定某些字段为马上载入(Eager Load)字段。比方当一个实体被读取时,该实体的相关实体也会被读取,像以下这样:

@OneToMany(mappedBy="stock", fetch=FetchType.EAGER)

private Collection optionsPrices;

对于@OneToOne和@ManyToOne类型的域。它们默认的载入方式就是马上载入。所以在须要改变这一行为时,使用fetch = FetchType.LAZY。相同的,马上载入对于JPA也是一个提示(Hint)。

当JPA读取对象的时候,假设该对象含有须要被马上载入的关联对象。

在非常多JPA的实现中,并不会使用JOIN语句在一条SQL中完毕全部对象的读取。它们会运行一条SQL命令首先获取到主要对象,然后生成一条或者多条语句来完毕其他关联对象的读取。当使用find方法时,无法改变这一默认行为。而在使用JPQL时。是可以使用JOIN语句的。

使用JPQL时,并不能指定须要选择一个对象的哪些域,比方以下的查询:

Query q = em.createQuery("SELECT s FROM StockPriceImpl s");

生成的SQL是这种:

SELECT FROM StockPriceTable

这也意味着当你不须要某些域时。仅仅能将它们声明为懒载入的域。

使用JPQL的JOIN语句可以通过一条SQL来得到一个对象和它的关联对象:

Query q = em.createQuery("SELECT s FROM StockOptionImpl s " + "JOIN FETCH s.optionsPrices");

以上的JPQL会生成例如以下的SQL:

SELECT t1., t0. FROM StockOptionPrice t0, StockPrice t1 WHERE ((t0.SYMBOL = t1.SYMBOL) AND (t0.PRICEDATE = t1.PRICEDATE))

JOIN FETCH和域是懒载入还是马上载入没有直接的关系。当JOIN FETCH了懒载入的域,那么这些域也会读取。然后在程序须要使用这些懒载入的域时,不会再去从数据库中读取。

当使用JOIN FETCH得到的全部数据都会被程序所使用时,它就能帮助提高程序的性能。

由于它降低了SQL的运行次数和数据库的訪问次数,这一般是一个使用了数据库的应用的瓶颈所在。

可是JOIN FETCH和JPA缓存的关系会有些微妙,在后面介绍JPA缓存时会讲述。

JOIN FETCH的其他实现方式

除了直接在JPQL中使用JOIN FETCH,还能够通过设置提示来实现。这样的方式在非常多JPA实现中被支持。比方:

Query q = em.createQuery("SELECT s FROM StockOptionImpl s");

q.setQueryHint("eclipselink.join-fetch", "s.optionsPrices");

在有些JPA实现中。还提供了一个@JoinFetch注解来提供JOIN FETCH的功能。

获取组(Fetch Group)

当一个实体对象有多个懒载入的域,那么在它们同一时候被须要时,JPA一般会为每一个别须要的域生成并运行一条SQL语句。

显而易见的是,在这样的场景下,生成并运行一条SQL语句会更好。

然而。JPA标准中并未定义这样的行为。可是大多数JPA实现都定义了一个获取组来完毕这一行为。即将多个懒载入域定义成一个获取组,每次载入它们中的随意一个时,整个组都会被载入。

所以,当须要这样的行为时,能够參考详细JPA实现的文档。

批量处理和查询(Batching and Queries)

JPA也能像JDBC处理ResultSet那样处理查询的结果:

一次性返回全部结果集中的全部记录

每次获取结果集中的一条记录

一次获取结果集中的N条记录(和JDBC的Fetch Size类似)

相同,这个Fetch Size也是和详细的JPA实现相关的,比方在EclipseLink和Hibernate中按例如以下的方式进行设置:

// EclipseLink

q.setHint("eclipselink.JDBC_FETCH_SIZE", "100000");

// Hibernate

@BatchSize

// Query here...

同一时候。能够对Query设置分页相关的设置:

Query q = em.createNamedQuery("selectAll");

query.setFirstResult(101);

query.setMaxResults(100);

List implements StockPrice> = q.getResultList();

这样就行只获取第101条到第200条这个区间的数据了。

同一时候。以上使用了命名查询(Named Query。createNamedQuery())而不是暂时查询(Ad-hoc Query。createQuery())。在非常多JPA实现中命名查询的速度要更快,由于一个命名查询会相应Statement Cache Pool中的一个PreparedStatement。剩下须要做的就仅仅是给该对象绑定參数。尽管对于暂时查询,也可以使用相同的实现方式,仅仅只是此时的JPQL仅仅有在执行时才可以知晓。所以实现起来比較困难,在非常多JPA实现中会为暂时查询新建一个Statement对象。

总结

JPA有一些优化选项能够限制(添加)单次数据库訪问的读取数据量。

对于BLOB和CLOB类型的字段,将它们的载入方式设置为懒载入。

JPA实体的关联实体能够被设置为懒载入或者马上载入。选择取决于应用的详细需求。

当须要马上载入实体的关联实体时,能够结合命名查询和JOIN语句。注意它对于JPA缓存的影响。

使用命名查询比暂时查询更快。

java jpa性能_[Java Performance] 数据库性能最佳实践 - JPA和读写优化相关推荐

  1. 20个数据库设计最佳实践

    2019独角兽企业重金招聘Python工程师标准>>> 数据库设计是整个程序的重点之一,为了支持相关程序运行,最佳的数据库设计往往不可能一蹴而就,只能反复探寻并逐步求精,这是一个复杂 ...

  2. TigerGraph | 图数据库建模最佳实践

    (一)引言 我们知道,家庭物品收纳整理有个重要的原则,就是分类.人们不会把所有的衣物乱堆在一起,而会按衣服的用途.颜色等等进行分类,这样衣物才会井然有序,容易取用. 类似的,现代企业的数据容量爆发式增 ...

  3. Mysql8官方分布式数据库MGR最佳实践

    MGR简介 MGR是mysql Group Replication简称,中文名称是Mysql组复制,MGR是MySQL官方于2016年12月推出的一个全新的高可用与高扩展的解决方案,提供了高可用.高扩 ...

  4. 一键导入excel到数据库的最佳实践

    一键导入excel到数据库的最佳实践 摘要:通常情况下,我们需要将excel数据导入到数据库(比如mysql,sql server,oracle)进行数据分析处理.通常我们会用像navicate这样的 ...

  5. sql server datetime取年月_快速定位数据库性能问题,RDS推出慢SQL统计分析

    在使用云的过程中,哪些指标最重要,是安全.弹性,还是计算能力? 其实这些都很关键.除此之外,云最重要的就是数据库了.数据库的性能直接关系到系统执行的效率和稳定性,更与业务紧密相关.如果数据库出现性能问 ...

  6. redis value多大会影响性能_事务对MySQL性能有什么影响?有无索引查找对其影响有多大?...

    推荐学习 阿里P8MySQL,基础/索引/锁/日志/调优都不误,一锅深扒端给你 抖音后端123面开挂,全靠这份啃了58天的「Java进阶核心知识集」 肝了30天,整出这份[分布式宝典:限流+缓存+通讯 ...

  7. java取负数_[Java] 告别“CV 工程师”码出高效!(基础篇)

    作为一名资深的 CV 工程师,某天,当我再一次日常看见满屏的报错信息与键盘上已经磨的泛白的 Ctrl.C.V 这三个按键时,我顿悟了. 百度谷歌复制粘贴虽然很香,但是总是依靠前人种树,终会有一天失去乘 ...

  8. mysql 树莓派3 性能_树莓派 3b 的性能怎么样?

    树莓派不是拼性能的设备,拼的是生态+低功耗+低价格. 3b的性能其实也挺不错了,不过还是不适合跑密集计算和图形计算. 再就是树莓派3b的网卡基于usb2.0的,所以带宽很低(网卡),所以不适合做多媒体 ...

  9. linux怎么查看数据库性能,正确评估SQL数据库性能,你必须知道的原理和方法!...

    原标题:正确评估SQL数据库性能,你必须知道的原理和方法! 作者:阿特 来源: http://blog.csdn.net/capsicum29/article/details/71480799 数据库 ...

最新文章

  1. Anroid基础建设之View,Window,Activity
  2. 编写wordcount程序
  3. java房产源码_基于jsp的房屋交易管理系统-JavaEE实现房屋交易管理系统 - java项目源码...
  4. 小甲鱼 OllyDbg 教程系列 (十三) : 把代码和变量注入程序 以及 硬件断点
  5. 论计算机网络的发展及运用,试论计算机网络发展及其应用研究
  6. 三角网格表面高斯曲率的计算与可视化
  7. java类转为er图_ER图,以及转化成关系模式
  8. Java jar 包免费下载(全)
  9. z11 max android 6.0,努比亚Z11Max 安卓6.0 魅族Flyme6刷机包 最新6.7.12.29R紫火版 20180108更新...
  10. 数学连乘和累加运算符号_数学所有的公式和符号
  11. 【嵌入式13】两台电脑串口通信
  12. 手机连接 linux系统软件,linux上安卓手机管理软件及连接办法
  13. 简单实用流程图模板分享,建议收藏
  14. c语言中nop的作用,单片机c语言编程里的nop()含义是什么?
  15. kafka-eagle-2.0.1安装及使用(超详细)
  16. unity制作滚动的天空简易版(在b站学的,此处总结,供学习)
  17. [JZOJ5594][min25筛]最大真因数
  18. 当审稿人给你的意见,你无法修改的时候怎么办哇,真的好绝望?
  19. python的SMTP
  20. 小程序近期频繁更新 背后原因全面解读

热门文章

  1. 前沿观察 | SageDB:一个自学成才的数据库
  2. nginx主模块指令
  3. curl-loader介绍
  4. lt、le、eq、ne、ge、gt的含义
  5. java线程----生产者和消费者问题
  6. 海量数据处理:如何从10亿个数中,找出最大的10000个数?(top K问题)
  7. C++ 动态线性表的顺序存储结构(数组实现)
  8. JVM详解之:汇编角度理解本地变量的生命周期
  9. java内存模型(JMM)和happens-before
  10. java中有界队列的饱和策略(reject policy)