对于大多数典型的 Spring/Hibernate 企业应用而言,其性能表现几乎彻底依赖于持久层的性能。此篇文章中将介绍如何确认应用是否受数据库约束,同时介绍七种经常使用的提升应用性能的速成法。本文系 OneAPM 工程师编译整理。html
确认应用是否受限于数据库的第一步,是在开发环境中进行测试,并使用 VisualVM 进行监控。VisualVM 是一款包含在 JDK 中的 Java 分析器,在命令行输入 jvisualvm
便可调用。java
启用 Visual VM 以后,尝试如下步骤:git
双击你正在运行的应用spring
选择 Sampler
sql
点击 Settings
复选框数据库
选择Profile only packages
,而后输入下列包:缓存
your.application.packages.*
性能优化
org.hibernate.*
bash
org.springframework.*
服务器
your.database.driver.package
, 好比 oracle.*
点击 Sample CPU
若是应用性能受限于数据库,其 CPU 分析结果看起来会像下图:
咱们看到,客户端 Java 进程花在等待数据库从网络中返回结果的时间占56%。
看到数据库查询是致使应用运行缓慢的缘由,实际上是好兆头。Hibernate 反射调用占比32.7%是正常状况,没法进一步优化。
性能调优的第一步是为程序定义基准运行,咱们要定义一组能有效执行的输入数据,让程序基准运行与生产环境下的运行差很少。
主要的区别在于基准运行的耗时要小不少。做为参考,5到10分钟的执行时间比较不错。
好的基准应该具有如下特征:
功能正确
输入数据的种类与生产环境下类似
在短期内执行完毕
基准运行的优化方案能够外推至完整运行
定义好的基准是成功解决问题的一半。
例如,经过批量运行处理通信系统的电话数据记录,选取10000条记录就是错误的作法。
缘由是:前10000条记录可能多为语音电话,而未知的性能问题可能发生在短信流量的处理过程当中。一开始若是基准不够好,就会致使错误的结论。
SQL 查询的执行语句与其执行时间能够经过 log4jdbc等方式收集。详细了解如何使用 log4jdbc 收集 SQL 查询信息,点击文章 使用 log4jdbc 优化 Spring/Hibernate 应用 SQL 日志。
查询的执行时间是从 Java 客户端收集的,该时间包含查询数据库的来回网络调用。SQL 查询的日志以下:
16 avr. 2014 11:13:48 | SQL_QUERY /* insert your.package.YourEntity */ insert into YOUR_TABLE (...) values (...) {executed in 13 msec}
预处理语句也是很重要的信息来源,它们经常会透露出经常使用的查询类型。了解更多的日志讯息,能够查看文章:Hibernate 为何/在何处使用该 SQL 查询?
SQL 日志能够回答下列问题:
哪些是执行过的最慢查询?
哪些是最经常使用的查询?
生成主键的耗时是多少?
是否有数据适合缓存?
对于大量的日志文件,最可行的解析方式就是使用命令行工具,该方法的好处是很是灵活,只要写一小段脚本或命令,咱们能够抽取出几乎大多数指标。只要你喜欢,任何命令行工具都适用。
如何你习惯了 Unix 命令行,bash 或是一个好选择。Bash 也能够在 Windows 工做站使用,Cygwin 或 Git 都包含了 bash 命令行。
下面介绍的速成法能找出 Spring/Hibernate 应用中常见的性能问题,以及对应的解决方案。
在插入操做频繁的进程中,主键的生成策略很重要。生成 id 的一种常见方法是使用数据库序列,一般一张表一个 id,从而避免在不一样表间进行插入时的冲突。
问题在于,若是要插入50条记录,咱们但愿为了获取这50个 id,能够避免50趟查询数据库的来回网络调用,让 Java 进程不一直等待。
Hibernate 提供了优化的 ID 生成器以免此问题。也即,对于序列,会默认使用 HiLo
id 生成器。如下是 HiLo 序列生成器的工做方式:
调用一次序列,得到 1000 (高值)
用如下方式计算50个 id
1000 * 50 + 0 = 50000
1000 * 50 + 1 = 50001
...
1000 * 50 + 49 = 50049, 达到低值 (50)
为新的高值1001调用序列,依次类推
所以一次序列调用,可生成50个键,从而减小数次来回网络调用致使的负担。
这些优化的键生成器默认在 Hibernate 4中开启。如要禁用,可将 hibernate.id.new_generator_mappings
设置为 false。
问题在于,若是你声明键生成策略为 AUTO
,且未启用优化的键生成器,那么应用最后会面临大量的序列调用。
为了确保启用优化的键生成器,请将键生成策略改成 SEQUENCE
而非 AUTO
。
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "your_key_generator") private Long id;
改变设定以后,在插入操做频繁的应用中能看到10%到20%的性能提高,并且几乎没有改动代码。
对于批处理程序,JDBC 驱动程序提供了旨在减小网络来回传输的优化方法:”JDBC batch inserts/updates“。使用该方法后,插入或更新会先在驱动层排队,而后再传送到数据库。
当达到阈值后,全部排队的语句都会一次性传给数据库。这能够避免驱动程序逐一传送语句,致使网络来回传送的负担。
通过如下配置,就能激活批处理 inserts/updates:
<prop key="hibernate.jdbc.batch_size">100</prop> <prop key="hibernate.order_inserts">true</prop> <prop key="hibernate.order_updates">true</prop>
仅设置 JDBC 批处理大小并不够。由于 JDBC 驱动程序只会在收到对同一张表 insert/updates 时批处理这些语句。
若是收到对一张新表的插入语句,JDBC 驱动程序会先清除对前一张表的批处理语句,而后开始分批处理针对新表的 SQL 语句。
Spring Batch 内置了类似的功能。该优化能在插入操做频繁的应用中带来30%到40%的性能提高,而不用改动任何代码行。
在向数据库添加或修改数据时,Hibernate 会在会话中保留一版已经存在的实体,以防在会话关闭以前这些实体再度被修改。
可是,多数状况下,一旦对应的插入操做已经在数据库中完成,咱们就能够安心地丢弃那些实体。这会释放 Java 客户端进程中的内存,避免太久的 Hibernate 会话致使的性能问题。
这种长久的会话应该尽可能避免。但若是出于某种缘由不得不使用它们,如下是控制内存消耗的方法:
entityManager.flush(); entityManager.clear();
flush
会触使新实体中的插入语句传送至数据库。clear
则会释放会话中的新实体。
Hibernate 内部使用了一种机制用于追踪被修改的实体,名为 dirty-checking。该机制并不基于实体类中的 equals 和 hashcode 方法。
Hibernate 尽量将 dirty-checking 的性能成本保持在最低值,只在须要时使用 dirty-check。可是该机制也有成本,在列数不少的表中该成本尤为可观。
在进行任何优化以前,最重要的是使用 VisualVM 测量 dirty-checking 的成本。
dirty-checking 能够经过如下方式禁用:
@Transactional(readOnly=true) public void someBusinessMethod() { .... }
禁用 dirty-checking 的另外一种方式是使用 Hibernate 无状态会话,预知详情请查看文档。
速成法5——搜索”坏“查询计划
检查最慢查询列表,看看有没有好的查询计划。最多见的”坏“查询计划包括:
全表搜索:一般缺乏一个索引或表统计过时时进行全表搜索。
全笛卡尔链接:意思是计算多张表的全笛卡尔乘积。检查一下缺乏的链接条件,或拆分为几个步骤以简化查询。
若是你使用批处理程序,提交间隔会对性能形成十倍甚至百倍的影响。
请确保提交间隔是符合预期的(对于 Spring 批任务,一般是100到1000之间)。常常,该参数的配置不正确。
若是一些数据能够缓存,则能够查看本文了解如何设置 Hibernate 缓存:Hibernate 二级/查询缓存的陷阱。
解决应用性能问题的关键,在于经过收集一些指标发现当前的瓶颈。
没有一些测量指标,每每没法在短期内找到真正的问题根源。
此外,不少典型的数据库驱动应用的性能陷阱,若是一开始就使用了 Spring Batch,就可以避免。
原文地址:http://blog.jhades.org/performance-tuning-of-spring-hibernate-applications/
OneAPM for Java 可以深刻到全部 Java 应用内部完成应用性能管理和监控,包括代码级别性能问题的可见性、性能瓶颈的快速识别与追溯、真实用户体验监控、服务器监控和端到端的应用性能管理。想阅读更多技术文章,请访问 OneAPM 官方博客