程序员必须练就的「性能调优」组合拳【4】

本系列前序文章索引:html

  • 程序员必须掌握的性能调优:老兵哥结合我的经历解释了程序员往架构师方向发展时为何要跨越性能调优这一关,以及介绍了从 X、Y、Z 三个维度优化性能的思路。
  • 从 X 维度优化系统的性能:老兵哥分享了从 X 维度优化系统性能的思路,包括让客户端分计算存储任务、优化交互设计等,主要是做为引子拓宽咱们性能调优的思路。
  • 应用容器 Tomcat 性能调优:老兵哥介绍了从 Y 维度经过优化应用容器 Tomcat 来优化系统性能的方法。
  • 开发框架 Spring 性能调优:老兵哥介绍了从 Y 维度经过优化开发框架 Spring 来优化系统性能的方法。

今天老兵哥将介绍经过优化对象关系映射 ORM 框架(Hibernate)等来优化系统性能的方法。程序员

4. ORM 框架 Hibernate

对象-关系映射 ORM(Object/Relation Mapping),是伴随着面向对象软件开发方法的发展而产生的。面向对象的开发方法是当今企业级应用开发环境中的主流方法,关系数据库是企业级应用环境中数据永久存储的主流数据存储系统。对象和关系是业务实体数据的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在数据库中,关系数据没法直接表达多对多关联和继承关系。数据库

对象-关系映射 ORM 系统一般以中间件的形式存在,借助描述对象到关系数据库数据的映射元数据,将内存中的对象自动持久化到关系数据库中,其本质就是将数据从一种形式转换到另一种形式。这个转换过程须要额外的开销,天然也就存在许多优化的机会,接下来咱们一块儿来看看如何提高 ORM 框架 Hibernate 的性能。
Hibernate 工做原理segmentfault

4.1 批量处理

应用或者 ORM 框架每次执行 SQL 语句都须要跟数据库创建链接,每次创建链接都须要额外开销。若是某个事务内部有循环屡次操做数据库的场景,那么将这些操做聚集在一块儿批量执行,这样就能够下降损耗,具体以下:缓存

  • 批量插入

使用这种方法时,首先在 Hibernate 的配置文件 hibernate.cfg.xml 中设置批量尺寸属性 hibernate.jdbc.batch_size ,且最好关闭Hibernate的二级缓存以提升效率。session

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.jdbc.batch_size">50</property> //设置尺寸
        <property name="hibernate.cache.use_second_level_cache">false</property> //关闭缓存
        <mapping resource="com/itlaobingge/po/User.hbm.xml" /> 
   </session-factory>
</hibernate-configuration>
public class HibernateDemo {
    public static void main(String args[]) {
        Session session = HibernateSessionFactory.getSession();
        Transaction ts = session.beginTransaction();
        for (int i = 0; i < 50; i++) {
            User user = new User();
            user.setPassword(i);
            session.save(user);
            if (i%50 == 0) {
               // 以 50 为一个批次往数据库提交,此值应与配置的批量尺寸一致
                session.flush();
                // 清空缓存区,释放内存供下批数据使用
                session.clear();  
            }
        }
        
        ts.commit();
        HibernateSessionFactory.closeSession();
    }
}
  • 批量更新

为了使 Hibernate 的 HQL 直接支持 update 的批量更新语法,咱们须要在 Hibernate 的配置文件 hibernate.cfg.xml 中设置 HQL/SQL 查询翻译器属性 "hibernate.query.factory_class":架构

<hibernate-configuration>
   ......
   <property name="hibernate.query.factory_class">
        org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory
    </property>
    <mapping resource="com/itlaobingge/po/User.hbm.xml" />
   </session-factory>
</hibernate-configuration>
public class HibernateDemo {
    public static void main(String args[]) {
        Session session = HibernateSessionFactory.getSession();
        Transaction ts = session.beginTransaction();
        Query query = session.createQuery("update User set password='123456'");
        query.executeUpdate();
        ts.commit();
        HibernateSessionFactory.closeSession();
    }
}
  • 批量删除

为了使 Hibernate 的 HQL 直接支持 delete 的批量更新语法,咱们须要在 Hibernate 的配置文件 hibernate.cfg.xml 中设置 HQL/SQL 查询翻译器属性 "hibernate.query.factory_class":并发

<hibernate-configuration>
   ......
   <property name="hibernate.query.factory_class">
        org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory
    </property>
    <mapping resource="com/itlaobingge/po/User.hbm.xml" />
   </session-factory>
</hibernate-configuration>
public class HibernateDemo {
    public static void main(String args[]) {
        Session session = HibernateSessionFactory.getSession();
        Transaction ts = session.beginTransaction();
        Query query=session.createQuery("delete User where id < 123");
        query.executeUpdate();
        ts.commit();
        HibernateSessionFactory.closeSession();
    }
}

4.2 抓取策略

抓取策略是指当应用程序须要在对象关联关系间进行导航时,Hibernate 如何获取关联对象的策略,常见的抓取策略有以下几种:app

  • 连接抓取(Join Fetching):经过在 select 语句中使用 out join 来获取对象的关联实例或者关联集合。
  • 查询抓取(Select Fetching):发送另一条 select 语句抓取当前对象的关联实体或者关联集合。除非咱们显示地指定 lazy=”false” 禁止延迟抓取,不然只有当咱们真正访问了关联关系时才会执行第二条 select 语句。
  • 子查询抓取:另外发送一条 select 语句抓取在前面查询到或抓取到的全部实体对象的关联集合。除非你显式的指定 lazy="false" 禁止延迟抓取,不然只有当你真正访问关联关系的时候,才会执行第二条 select 语句。
  • 批量抓取(Batch fetching):对查询抓取的优化方案,经过指定一个主键或外键列表,Hibernate 使用单条 select 语句获取一批对象实例或集合。

Hibernate 会区分下列几种状况:框架

  • 当即抓取(Immediate fetching):当宿主被加载时,关联、集合或属性被当即抓取。
  • 延迟集合抓取(Lazy collectionfetching):直到应用程序对集合进行了一次操做时,集合才被抓取。
  • Extra-lazy 集合抓取(Extra-lazy collection fetching):对集合类中的每一个元素而言,都是直到须要时才去访问数据库。除非绝对必要,Hibernate 不会试图去把整个集合都抓取到内存里来。
  • 代理抓取(Proxy fetching):对返回单值的关联而言,当其某个方法被调用,而非对其关键字进行 get 操做时才抓取。
  • 非代理抓取(No-proxy fetching):对返回单值的关联而言,当实例变量被访问的时候进行抓取。与上面的代理抓取相比,这种方法没有那么延迟得厉害,就算只访问标识符,也会致使关联抓取,可是更加透明,由于对应用程序来讲,再也不看到 proxy。这种方法须要在编译期间进行字节码加强操做,所以不多须要用到。
  • 属性延迟加载(Lazy-attribute fetching):对属性或返回单值的关联而言,当其实例变量被访问的时候进行抓取。须要编译期字节码强化,所以这一方法不多是必要的。

定制合理的抓取策略对系统的性能提高有很大的帮助。查询抓取在 N+1 查询的状况下是极其脆弱的,所以咱们可能会要求在映射文件中定义链接抓取(fetch=”join”),可是在映射文件中定义的抓取策略将会产生如下影响:经过 get() 或者 load() 方法获取数据,只有在关联之间进行导航时,才会隐式的取得数据。

条件查询,使用了 subselect 抓取的 HQL 查询,无论使用哪一种抓取策略,定义为非延时的类图会保证装载入内存,这就意味着一条 HQL 查询后紧跟着一系列的查询。一般咱们并不使用映射文件进行抓取策略的定制,更可能是保持其默认值而后在待定事务中适用 HQL 的左链接对其进行重载。

Hibernate 推荐的作法也是最佳实践:把全部对象关联的抓取都设为 lazy,而后在特定事务中进行重载。这种考虑是基于对象之间的关联关系错综复杂,有时候哪怕咱们只是一个简单的查询,也会致使不少关联对象被装载出来,因此在 Hibernate 中,全部对象关联都是 lazy 的。

在 Hibernate 中实施关联抓取,咱们能够定义每次抓取数据的数量,批量地将数据载入内存,减小与数据库交互的次数,在应用程序中能够定义默认的关联抓取数量。Hibernate 提供了两种批量抓取方案:

  • 类级别的批量查询,若是一个 Session 中须要载入 30 个 User 实例,在 User 中拥有一个类 Class 成员变量 class。若是 lazy=“true”,咱们须要遍历整个 user 集合,每个 user 都须要 getClass(),在默认状况下要执行 30 次查询获得 Class 对象。所以,能够经过在映射文件的 Class 属性设置 batch-size,这样Hibernate 只须要执行两次查询便可:
<class name=”Class” batch-size=”15”>...</class>
  • 集合级别的批量查询,若是咱们须要遍历 30 个 Class 对象下所拥有 User 对象列表,在 Session 中须要载入 30 个 Class 对象,遍历 Class 集合将会引发 30 次查询,每次查询都会调用 getUsers()。若是在 Class 的映射定义中,容许对 User 进行批量抓取,则 Hibernate 就会预先加载整个集合。
<set name=”users” batch-size=”15”>...</set>

4.3 二级缓存

缓存能够下降应用程序对物理数据源访问的频次,从而提升应用程序的运行性能。缓存对 Hibernate 来讲也是很重要的,它使用了以下图所示的多级缓存方案:
Hibernate 二级缓存

  • 一级缓存,第一级缓存是 Session 缓存,属于强制性缓存,全部请求都必须经过它。Session 对象在它本身的权利之下,在将它提交给数据库以前保存一个对象。若是你对一个对象发出多个更新,Hibernate 会尝试尽量长地延迟更新来减小发出的 SQL 更新语句的数目。若是你关闭 Session,全部缓存的对象丢失,或是存留,或是在数据库中被更新。
  • 二级缓存,第二级缓存是可选择的,第一级缓存在任何想要在第二级缓存中找到一个对象前被询问。第二级缓存能够在每个类和每个集合的基础上被安装,而且它主要负责跨会话缓存对象。任何第三方缓存均可以和 Hibernate 合做,只要它实现 org.hibernate.cache.CacheProvider 接口。

Hibernate 的二级缓存经过两个步骤设置:第一,你必须决定好使用哪一个并发策略(Transactional、Read-write、Nonstrict-read-write、Read-only);第二,你使用第三方缓存提供者来配置缓存到期时间和物理缓存属性。并发策略,负责保存缓存中的数据项和从缓存中检索它们,如何选择并发策略及配置能够查资料。

4.4 查询缓存

查询结果集也能够被缓存,只有在常用一样的参数进行查询时,查询缓存才会有些用处。若是要使用查询缓存,你必须打开它:hibernate.cache.use_query_cache,该设置将会建立两个缓存区域:一个用于保存查询结果集(org.hibernate.cache.StandardQueryCache);另外一个则用于保存最近查询的一系列表的时间戳(org.hibernate.cache.UpdateTimestampsCache)。

在查询缓存中,它并不缓存结果集中所包含的实体的确切状态,它只缓存这些实体的标识符属性的值、以及各值类型的结果,因此查询缓存一般会和二级缓存一块儿使用。绝大多数的查询并不能从查询缓存中受益,因此 Hibernate 默认是不进行查询缓存的。如若须要进行缓存,请调用 Query.setCacheable(true) 方法。这个调用会让查询在执行过程当中时先从缓存中查找结果,并将本身的结果集放到缓存中去。

关注「 IT老兵哥 」,赋能程序人生!近期热评系列《 程序员必须懂的架构师入门课 》:

IT老兵哥

  1. 架构究竟是什么,你知道吗?
  2. 架构都有哪些,我该怎么选?
  3. 架构师都干什么,你知道吗?
  4. 练就哪些技能才胜任架构师?
  5. 怎样才能搞定上下游的客户?
  6. 如何从开发岗转型作架构师?
  7. 程序员为何必需要懂架构?
相关文章
相关标签/搜索