为啥休眠在获取较少行数时较慢?

Posted

技术标签:

【中文标题】为啥休眠在获取较少行数时较慢?【英文标题】:Why hibernate is slower in fetching lesser number of rows?为什么休眠在获取较少行数时较慢? 【发布时间】:2016-04-28 20:26:13 【问题描述】:

我正在关注此处列出的休眠教程http://docs.jboss.org/hibernate/orm/5.1/quickstart/html_single/#tutorial-native。

我已修改代码以使用本地 mysql 作为数据库。之后,我用 10000 行填充了数据库表。

我比较了两种类型的数据库读取的延迟 - 一种通过休眠原生查询;其他通过直接 JDBC 并从 ResultSet 创建对象。

看到 hibernate 与我的自定义 JDBC 和 java 对象映射实现相比非常慢,我觉得很奇怪。当获取的行数低于 10000 时会发生这种情况。例如,使用我的方法获取 10-100 行需要 3-18 毫秒,而休眠需要 280-320 毫秒左右。但是当我尝试获取 >10K 行时,hibernate 变得高效。

有人可以解释一下 hibernate 正在做什么导致这么多延迟吗?

我的 hibernate.cfg.xml 如下所示

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
    "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>

    <!-- Database connection settings -->
    <property name="connection.url">jdbc:mysql://localhost:3306/fquick-task-manager?useSSLx`=false</property>
    <property name="connection.username">root</property>
    <property name="connection.password"/>

    <!-- JDBC connection pool (use the built-in) -->
    <property name="connection.pool_size">15</property>

    <!-- SQL dialect -->
    <property name="dialect">org.hibernate.dialect.MySQLDialect</property>

    <!-- Disable the second-level cache  -->
    <property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>

    <!-- Echo all executed SQL to stdout -->
    <property name="show_sql">true</property>

    <!-- Drop and re-create the database schema on startup -->
    <property name="hbm2ddl.auto">update</property>

    <mapping resource="org/hibernate/tutorial/hbm/Event.hbm.xml"/>

</session-factory>
</hibernate-configuration>

我的测试函数如下所示

public void testBasicUsage() 

    // Using JDBC
    Session session = sessionFactory.openSession();
    session.beginTransaction();
    String queryStr = "";
    try 
        long start = System.currentTimeMillis();
        Statement statement = ((SessionImpl) session).connection().createStatement();
        queryStr = "select * from Events where EVENT_ID < 10";
        ResultSet rs = statement.executeQuery(queryStr);
        List<Event> events = new ArrayList<Event>();
        while (rs.next()) 
            Long eventId = rs.getLong("EVENT_ID");
            String title = rs.getString("title");
            Date myDate = rs.getDate("EVENT_DATE");
            Event event = new Event(eventId,title ,myDate);
            events.add(event);
        
        long end = System.currentTimeMillis();
        long timeTaken = end - start;
        System.out.println("Query took " + timeTaken + "ms");

     catch (SQLException e) 
        System.out.println("Error in statement creation");
    
    session.getTransaction().commit();
    session.close();

    // Using Hibernate
    session = sessionFactory.openSession();
    session.beginTransaction();
    queryStr = "select * from Events where EVENT_ID > 20 & EVENT_ID < 30";
    long start3 = System.currentTimeMillis();
    session.createSQLQuery(queryStr).list();
    long end3 = System.currentTimeMillis();
    long timeTaken3 = end3 - start3;
    System.out.println("Query took " + timeTaken3 + "ms");
    session.getTransaction().commit();
    session.close();


【问题讨论】:

我都试过了。获取相同的行集以及不同的 10 行、50 行、100 行组合。结果是一样的。每次在数据库中花费的测量时间在 5ms 以内。休息在 ORM 中度过。我有兴趣了解休眠情况下的时间分布。在持久化上下文中保存对象需要多少时间,创建对象需要多少时间以及为什么在行数较少的情况下执行速度较慢。 【参考方案1】:

1:根据您的 hibernate 属性,Hibernate 将尝试重新创建数据库模式,这需要查询元数据、计算差异、应用差异(如果需要),这在直接 JDBC 中是无法完成的。任何比较都是无效的。

2:多种技术之间的任何性能比较都需要一个预热期,因为有各种幕后设置(连接到数据库、解析 SQL 语句、解释元数据等)。您还包括一个连接池大小,这取决于您使用休眠的方式,以后可能会给休眠带来不公平的优势。

3:您没有包含 Hibernate 映射文件或带有 JPA 注释的对象。即使您在此处的基本 SQL 语句没有连接,但如果您在对象中定义了关系,Hibernate 会考虑到这一点,因此,这可能不是一个公平的比较。

4:在启动期间,Hibernate 将连接到数据库,加载映射文件/对象,确保一切都对齐并且数据库和持久性对象可用。如果您使用错误的语法、不正确的表/列名称等命名查询,则应该对其进行识别。它还需要您的持久性对象并执行一些动态字节生成/CGLIB 的工作,以使该对象(至少在工作表下)不仅仅是一个普通的 POJO(至少我们认为它是这样的)。

5:当 Hibernate 被要求获取数据时,它会创建 SQL 语句并将结果直接绑定到对象中。所以很明显有一些开销,但一旦完成它就会更有效率。在您的直接 JDBC 循环中,每次您需要搜索以查找 getLong、getString、getDate 等正在返回的列,而不是先找出列号,然后随后使用直接索引。这可能是罪魁祸首,Hibernate 需要一点时间才能有效地设置所有内容,然后由于创建对象的效率最终超过了原始 JDBC。

6:作为一个抽象层,Hibernate 总是比编写良好的直接 JDBC 应用程序(本示例不是)慢。但是,开发时间更少,错误更少,代码的整体质量应该更好。您只需要在其限制范围内工作即可。

【讨论】:

我不知道为什么所有的东西都变粗了,很抱歉。 感谢您的回复。第 1 点仅在应用程序启动时有效,因此不会影响查询时间。对于第 3 点,这是我的课程ideone.com/UhwEOM 和我的映射文件ideone.com/BqzJzs,它没有任何关联。确实,hibernate 做了很多事情,比如在持久化上下文中保存对象,使用反射来查找对象字段以及其他东西。我想知道通过休眠获得更少行(即 10 行)的时间分布。 hibernate 内部的哪些构造使用了多少时间,因此即使对于较少的行,也需要 300 毫秒。

以上是关于为啥休眠在获取较少行数时较慢?的主要内容,如果未能解决你的问题,请参考以下文章

删除后休眠意外行数上升

如何关闭休眠模式

无法更新记录休眠

从休眠配置创建 EntityManagerFactory

为啥在更新事务期间休眠调用删除?

为啥休眠不能执行命令