【问题标题】:Why are bulk inserts with JPA/Eclipselink/Texo much(!) slower than with pure JDBC insert?为什么使用 JPA/Eclipselink/Texo 的批量插入比使用纯 JDBC 插入要慢得多(!)?
【发布时间】:2017-10-22 02:42:06
【问题描述】:

我有大量使用 EMF/Texo 组合生成和注释的类。我使用 JPA/Eclipselink 将它们保存在 SQL Server 数据库中。

这很好用,但是当需要持久化大量对象时性能很差。所以我写了两个测试用例(参见TestBulkInserts.java),它们比较了使用框架(foo)的大容量插入和普通的JDBC大容量插入(bar)的性能。

当插入 10000 个对象时,这是低于平均大小的批量插入。 foo()bar() 给出以下时间:

  • JPA/Texo 持续时间:19.620 毫秒

  • 纯 JDBC 持续时间:892 毫秒

我想知道为什么会有如此巨大的差异(超过 20 倍!)。更大的尺寸甚至会变得更糟。

DatabaseObject 类扩展了 PersistableObjectClass.java(见下文),两者都是使用 Texo + EMF 生成的(包括各自的 DAO 类)。

我没有在persistence.xml 中添加任何特定设置,除了必要的连接细节。

TestBulkInserts.java:

import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
...
import com.ownproject.loader.generated.DbModelPackage;
import com.ownproject.loader.DatabaseObject;
import com.ownproject.loader.dao.DatabaseObjectDao;
import javax.persistence.Persistence;
import org.eclipse.emf.texo.server.store.EntityManagerProvider;
import org.junit.Test;

public class TestBulkInserts {

 private static final int NUM_LOOPS = 10000;

 @Test
 public void foo() {
  TestMethods.connectTestDBandEMF();
  // basically does this
  // DbModelPackage.initialize();
  // EntityManagerProvider.getInstance().setEntityManagerFactory(Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_TEST));

  Stopwatch sw = Stopwatch.createStarted();

  DatabaseObjectDao dao = new DatabaseObjectDao();
  dao.getEntityManager().getTransaction().begin();
  for (int i = 0; i < NUM_LOOPS; i++) {
    DatabaseObject dbo = new DatabaseObject();
    dbo.setString(UUID.randomUUID().toString());
    dbo.setInsert_time(Date.valueOf(LocalDate.now()));
    dao.insert(dbo);
  }
  dao.getEntityManager().getTransaction().commit();

  sw.stop();
  System.out.println(String.format("Duration JPA/Texo: %,dms", sw.elapsed(TimeUnit.MILLISECONDS)));
  }

 @Test
 public void bar() throws ClassNotFoundException, SQLException {
  Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
  String connectionUrl = "jdbc:sqlserver://hostname:1433;databaseName=local_test;user=sa;password=blablub;";
  Connection con = DriverManager.getConnection(connectionUrl);
  con.setAutoCommit(false);

  Stopwatch sw = Stopwatch.createStarted();

  PreparedStatement insertStatement = con.prepareStatement("INSERT INTO DatabaseObject(b_id, insert_time) VALUES (?, ?)");
  for (int i = 0; i < NUM_LOOPS; i++) {
    insertStatement.setString(1, UUID.randomUUID().toString());
    insertStatement.setDate(2, Date.valueOf(LocalDate.now()));
    insertStatement.addBatch();
  }
  insertStatement.executeBatch();
  con.commit();
  con.close();

  sw.stop();
  System.out.println(String.format("Duration plain JDBC: %,dms", sw.elapsed(TimeUnit.MILLISECONDS)));
  }
}

PersistableObjectClass.java:

import javax.persistence.Basic;
...
import javax.persistence.TemporalType;

@Entity(name = "PersistableObjectClass")
@MappedSuperclass()
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class PersistableObjectClass {

  @Basic()
  @Temporal(TemporalType.TIMESTAMP)
  private Date insert_time = null;

  @Id()
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private int s_id = 0;

...
}

【问题讨论】:

  • 感谢您的提示!你是说内存问题吗?因为刷新并没有减少加载时间。
  • JPA 增加了开销,因此可以预料会有一些差异,但是有太多变量无法解释您的数字。您是否打开了登录以查看生成的 SQL,以确保您确实在进行苹果对苹果的比较?您在 JPA/EclipseLink 中使用了哪些设置?为什么您使用 Identity 进行序列管理,而不是允许与您的批量大小匹配的预分配?

标签: java jpa jdbc eclipselink emf


【解决方案1】:

不仅需要使用批量更新,还需要确保定期提交事务,否则会遇到长时间运行的事务,这对2PL 都是不利的或MVCC 数据库引擎。

所以,批处理作业应该是这样的:

int entityCount = 50;
int batchSize = 25;
 
EntityManager entityManager = null;
EntityTransaction transaction = null;
 
try {
    entityManager = entityManagerFactory()
        .createEntityManager();
 
    transaction = entityManager.getTransaction();
    transaction.begin();
 
    for ( int i = 0; i < entityCount; ++i ) {
        if ( i > 0 && i % batchSize == 0 ) {
            entityManager.flush();
            entityManager.clear();
 
            transaction.commit();
            transaction.begin();
        }
 
        Post post = new Post( 
            String.format( "Post %d", i + 1 ) 
        );
        entityManager.persist( post );
    }
 
    transaction.commit();
} catch (RuntimeException e) {
    if ( transaction != null && 
         transaction.isActive()) {
        transaction.rollback();
    }
    throw e;
} finally {
    if (entityManager != null) {
        entityManager.close();
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-16
    • 2010-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-23
    相关资源
    最近更新 更多