【问题标题】:JPA with HIBERNATE insert very slow带有 HIBERNATE 的 JPA 插入速度非常慢
【发布时间】:2013-11-27 18:47:19
【问题描述】:

我正在尝试使用 JAP 和 HIBERNATE 将一些数据插入 SQL Server 2008 R2。一切都“有效”,除了它非常慢。插入 20000 行大约需要 45 秒,而 C# 脚本大约需要不到 1 秒。

该领域的任何资深人士都可以提供一些帮助吗?我将不胜感激。

更新:从下面的答案中得到了一些很好的建议,但它仍然无法按预期工作。速度是一样的。

这是更新后的 persistence.xml:

<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="ClusterPersist"
    transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>cluster.data.persist.sqlserver.EventResult</class>
    <exclude-unlisted-classes>true</exclude-unlisted-classes>
    <properties>
        <property name="javax.persistence.jdbc.url"
            value="jdbc:sqlserver://MYSERVER:1433;databaseName=MYTABLE" />
        <property name="javax.persistence.jdbc.user" value="USER" />
        <property name="javax.persistence.jdbc.password" value="PASSWORD" />
        <property name="javax.persistence.jdbc.driver"
            value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
        <property name="hibernate.show_sql" value="flase" />
        <property name="hibernate.hbm2ddl.auto" value="update" />

        <property name="hibernate.connection.provider_class"
            value="org.hibernate.service.jdbc.connections.internal.C3P0ConnectionProvider" />

        <property name="hibernate.c3p0.max_size" value="100" />
        <property name="hibernate.c3p0.min_size" value="0" />
        <property name="hibernate.c3p0.acquire_increment" value="1" />
        <property name="hibernate.c3p0.idle_test_period" value="300" />
        <property name="hibernate.c3p0.max_statements" value="0" />
        <property name="hibernate.c3p0.timeout" value="100" />
        <property name="hibernate.jdbc.batch_size" value="50" />
        <property name="hibernate.cache.use_second_level_cache" value="false" />
    </properties>
</persistence-unit>

这是更新的代码部分:

public static void writeToDB(String filePath) throws IOException {

    EntityManager entityManager = entityManagerFactory.createEntityManager();
    Session session = (Session) entityManager.getDelegate();
    Transaction tx = session.beginTransaction();
    int i = 0;

    URL filePathUrl = null;
    try {
        filePathUrl = new URL(filePath);
    } catch (MalformedURLException e) {
        filePathUrl = (new File(filePath)).toURI().toURL();
    }

    String line = null;
    BufferedReader stream = null;

    try {
        InputStream in = filePathUrl.openStream();
        stream = new BufferedReader(new InputStreamReader(in));


        // Read each line in the file
        MyRow myRow = new MyRow();
        while ((line = stream.readLine()) != null) {
            String[] splitted = line.split(",");
            int num1 = Integer.valueOf(splitted[1]);
            float num2= Float.valueOf(splitted[6]).intValue();

            myRow.setNum1(num1);
            myRow.setNum2(num2);

            session.save(myRow);

            if (i % 50 == 0) { 
                session.flush();
                session.clear();
            }

            i++;

        }
        tx.commit();

    } finally {
        if (stream != null)
            stream.close();
    }
    session.close();

}

已更新,这是 MyRow 的来源:

@Entity
@Table(name="MYTABLE")
public class MyRow {    

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY) 
private Long id;

@Basic
@Column(name = "Num1")
private int Num1;

@Basic
@Column(name = "Num2")
private float Num2;

public Long getId() {
    return id;
}

public void setId(Long id) {
    this.id = id;
}

public float getNum1() {
    return Num1;
}

public void setNum1(float num1) {
    Num1 = num1;
}

public int getNum2() {
    return Num2;
}

public void setNum2(int num2) {
    Num2 = num2;
}
}

【问题讨论】:

    标签: sql sql-server database hibernate jpa


    【解决方案1】:

    问题

    如果您使用 Hibernate 作为 ORM,主要的性能损失之一是其“脏检查”的实现方式(因为没有字节码增强,这是所有基于 JDO 的 ORM 和其他一些标准的标准,脏检查将始终是低效的黑客攻击)。

    刷新时,需要对会话中的每个对象进行脏检查,以查看它是否“脏”,即自数据库加载后,其属性之一已更改。对于所有“脏”(已更改)对象,Hibernate 必须生成 SQL 更新以更新表示脏对象的记录。

    Hibernate 脏检查在除少量对象之外的任何对象上都非常慢,因为它需要在内存中的对象之间执行“逐个字段”比较,并在对象首次从数据库加载时拍摄快照。加载的对象越多,例如显示页面的 HTTP 请求,调用 commit 时需要的脏检查就越多。

    Hibernate 脏检查机制的技术细节

    您可以在此处阅读有关 Hibernate 的脏检查机制以“逐个字段”比较实现的更多信息:

    How does Hibernate detect dirty state of an entity object?

    问题在其他 ORM 中是如何解决的

    其他一些 ORM 使用的更有效的机制是使用自动生成的“脏标志”属性而不是“逐字段”比较,但这传统上只在使用并促进字节码增强或字节码“编织”,因为它有时被称为 http://datanucleus.org

    在字节码增强期间,通过 DataNucleus 或任何其他支持此功能的 ORM,每个实体类都被增强为:

    • 添加隐式脏标志属性
    • 将代码添加到类中的每个 setter 方法中,以便在调用时自动设置脏标志

    然后在刷新期间,只需要检查脏标志而不是逐个字段进行比较 - 正如您可以想象的那样,这要快几个数量级。

    “逐个字段”脏检查的其他负面后果

    Hibernate 脏检查的另一个低效之处是需要在内存中保存每个加载对象的快照,以避免在脏检查期间重新加载和检查数据库。

    每个对象快照都是其所有字段的集合。

    除了刷新时 Hibernate 脏检查机制的性能损失之外,该机制还会给您的应用程序带来额外的内存消耗和 CPU 使用负担,这些消耗与实例化和初始化从数据库加载的每个对象的这些快照相关联- 根据您的应用程序,可能会达到数千或数百万。

    Hibernate 引入了字节码增强功能来解决这个问题,但我参与过许多 ORM 持久化项目(包括 Hibernate 和非 Hibernate),我还没有看到使用该特性的 Hibernate 持久化项目,可能是由于多种原因:

    • Hibernate 传统上在人们评估 ORM 技术时将其“不需要字节码增强”作为一项功能进行宣传
    • Hibernate 的字节码增强实现的历史可靠性问题可能不如从一开始就使用和促进字节码增强的 ORM 成熟
    • 由于提倡反“字节码增强”的立场,以及某些群体在 ORM 早期向人们灌输使用字节码增强的恐惧,一些人仍然害怕使用字节码增强

    如今,字节码增强用于许多不同的事情——不仅仅是持久性。它几乎已成为主流。

    【讨论】:

      【解决方案2】:

      要启用JDBC batching,您应该将属性 hibernate.jdbc.batch_size 初始化为 10 到 50 之间(仅限 int)

      hibernate.jdbc.batch_size=50
      

      如果它仍然没有预期的那么快,那么我会查看上面的文档,注意注意事项和第 4.1 节。特别是注释说:“如果您使用身份标识符生成器,​​Hibernate 会透明地禁用 JDBC 级别的插入批处理。”

      【讨论】:

      • 请看我的cmets到@SJuan76。这里一定少了点什么。
      • 您是否尝试使用匿名事务句柄?将它们存储在变量中。您可以编辑您的问题以指示最新代码吗?
      • 您不是对文件读取和插入进行计时吗?尝试隔离,因为它看起来就像您一次阅读一行。另外,请添加您的 MyRow 来源。
      • 我确实看到了文件读取部分。它是本地的,因此读取文件的速度非常快。我已经包含了 MyRow 源代码。非常感谢。
      • 我将身份管理器从标识符生成器更改为手动。而且它的工作速度要快得多。顺便说一句,有什么建议可以使用批量插入的良好标识符生成器吗?还请更新您的答案,这样人们就不必通读 cmets 来了解发生了什么。
      【解决方案3】:

      老话题,但今天遇到了这个寻找别的东西。我不得不发布这个常见的问题,不幸的是,这个问题没有得到很好的理解和记录。很长一段时间以来,Hibernate 的文档只有上面发布的那个简短说明。 从版本 5 开始,有一个更好但仍然很薄的解释:https://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#identifiers-generators-identity

      超大集合插入慢的问题就是Id生成策略选择不当:

      @Id
      @GeneratedValue(strategy=GenerationType.IDENTITY) 
      

      在使用Identity策略时,需要理解的是数据库服务器在物理插入上创建行的标识。 Hibernate 需要知道分配的 Id 以使对象在会话中处于持久状态。数据库生成的 Id 仅在插入的响应中知道。 Hibernate 没有选择,只能执行 20000 次单独的插入才能检索生成的 Id。据我所知,它不适用于批处理,不适用于 Sybase,不适用于 MSSQL。这就是为什么,无论您多么努力并且正确配置了所有批处理属性,Hibernate 都会执行单独的插入。

      我知道并多次应用的唯一解决方案是选择客户端 ID 生成策略,而不是流行的数据库端身份策略。 我经常使用:

      @Id
      @GeneratedValue(strategy = GenerationType.SEQUENCE)
      @GenericGenerator(strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator")
      

      还有一些配置可以让它工作,但这就是它的本质。当使用客户端 ID 生成时,Hibernate 将在访问数据库之前设置所有 20000 个对象的 ID。并且如之前的答案中所见,具有适当的批处理属性,Hibernate 将按预期进行批量插入。

      不幸的是,身份生成器如此方便和流行,它在所有示例中随处可见,但没有明确解释使用此策略的后果。我阅读了许多所谓的“高级”Hibernate 书籍,但迄今为止从未见过一本解释 Identity 对大型数据集的基础插入性能的影响。

      【讨论】:

        【解决方案4】:

        休眠“默认模式”很慢。

        它的优点是对象关系映射和一些缓存(但显然它对于批量插入不是很有用)。

        改用批处理http://docs.jboss.org/hibernate/core/4.0/devguide/en-US/html/ch04.html

        【讨论】:

        • 非常感谢。您能否就如何设置persistence.xml 设置一个简短的示例?更复杂的一层是我使用了 JPA,因此某些设置可能不容易转换为 persistence.xml。
        • 我提供的链接指出Before batch processing, enable JDBC batching. To enable JDBC batching, set the property hibernate.jdbc.batch_size to an integer between 10 and 50.
        • 刚刚试了一下,还是一样的速度。这就是我在 persistence.xml “”中添加的内容。我相应地修改了代码作为链接中的示例。代码如下所示:'Session session = (Session) entityManager.getDelegate();交易 tx = session.beginTransaction();'
        • 这行得通。冲洗然后清除可提供稳定和线性的性能。但是不确定后果是什么。 bathc_size 50 据我所知没有效果。
        猜你喜欢
        • 2012-02-25
        • 2016-05-22
        • 2011-08-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-11-25
        • 1970-01-01
        相关资源
        最近更新 更多