Нужно вставить 100000 строк в mysql, используя hibernate менее чем за 5 секунд

Я пытаюсь вставить 100 000 строк в таблицу MYSQL под 5 секундами с помощью Hibernate (JPA). Я пробовал каждый трюк hibernate предлагает и до сих пор не может сделать лучше, чем 35 секунд.

1-я оптимизация: я начал с генератора последовательности идентификаторов, который привел к 60 секундам для вставки. Позже я отказался от генератора последовательности и начал назначать @Id поле себя, прочитав MAX(id) и с помощью AtomicInteger.incrementAndGet() назначить поля самостоятельно. Это уменьшило вставку время до 35 секунд.

2-я оптимизация: я включил пакетные вставки, добавив

<prop key="hibernate.jdbc.batch_size">30</prop> <prop key="hibernate.order_inserts">true</prop> <prop key="hibernate.current_session_context_class">thread</prop> <prop key="hibernate.jdbc.batch_versioned_data">true</prop>

конфигурации. Я был потрясен, обнаружив, что пакетные вставки абсолютно ничего не сделали, чтобы уменьшить время вставки. Прошло еще 35 секунд!

сейчас, я думаю о попытке вставить с помощью нескольких потоков. У кого-нибудь есть указания? Должен ли я был выбрать MongoDB?

ниже моя конфигурация: 1. Зимовать конфигурация `

<bean id="entityManagerFactoryBean" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan" value="com.progresssoft.manishkr" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
        </property>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
                <prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
                <prop key="hibernate.jdbc.batch_size">30</prop>
                <prop key="hibernate.order_inserts">true</prop>
                <prop key="hibernate.current_session_context_class">thread</prop>
                <prop key="hibernate.jdbc.batch_versioned_data">true</prop>
            </props>
        </property>
    </bean>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"
          id="dataSource">
        <property name="driverClassName" value="${database.driver}"></property>
        <property name="url" value="${database.url}"></property>
        <property name="username" value="${database.username}"></property>
        <property name="password" value="${database.password}"></property>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactoryBean" />
    </bean>



    <tx:annotation-driven transaction-manager="transactionManager" />

`

  1. объект конфигурации :

`

@Entity
@Table(name = "myEntity")
public class MyEntity {

    @Id
    private Integer id;

    @Column(name = "deal_id")
    private String dealId;

    ....
    ....

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "timestamp")
    private Date timestamp;

    @Column(name = "amount")
    private BigDecimal amount;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "source_file")
    private MyFile sourceFile;

    public Deal(Integer id,String dealId, ....., Timestamp timestamp, BigDecimal amount, SourceFile sourceFile) {
        this.id = id;
        this.dealId = dealId;
        ...
        ...
        ...
        this.amount = amount;
        this.sourceFile = sourceFile;
    }


    public String getDealId() {
        return dealId;
    }

    public void setDealId(String dealId) {
        this.dealId = dealId;
    }

   ...

   ...


    ....

    public BigDecimal getAmount() {
        return amount;
    }

    public void setAmount(BigDecimal amount) {
        this.amount = amount;
    }

    ....


    public Integer getId() {
        return id;
    }

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

`

  1. сохранение кода (услуга) :

`

@Service
@Transactional
public class ServiceImpl implements MyService{

    @Autowired
    private MyDao dao;
....

`void foo(){
        for(MyObject d : listOfObjects_100000){
            dao.persist(d);
        }
}

` 4. Класс Dao:

`

@Repository
public class DaoImpl implements MyDao{

    @PersistenceContext
    private EntityManager em;

    public void persist(Deal deal){
        em.persist(deal);
    }
}

`

журналы: `

DEBUG o.h.e.j.b.internal.AbstractBatchImpl - Reusing batch statement
18:26:32.906 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - insert into deal (amount, deal_id, timestamp, from_currency, source_file, to_currency, id) values (?, ?, ?, ?, ?, ?, ?)
18:26:32.906 [http-nio-8080-exec-2] DEBUG o.h.e.j.b.internal.AbstractBatchImpl - Reusing batch statement
18:26:32.906 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - insert into deal (amount, deal_id, timestamp, from_currency, source_file, to_currency, id) values (?, ?, ?, ?, ?, ?, ?)
18:26:32.906 [http-nio-8080-exec-2] DEBUG o.h.e.j.b.internal.AbstractBatchImpl - Reusing batch statement
18:26:32.906 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - insert into deal (amount, deal_id, timestamp, from_currency, source_file, to_currency, id) values (?, ?, ?, ?, ?, ?, ?)
18:26:32.906 [http-nio-8080-exec-2] DEBUG o.h.e.j.b.internal.AbstractBatchImpl - Reusing batch statement
18:26:32.906 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - insert into deal (amount, deal_id, timestamp, from_currency, source_file, to_currency, id) values (?, ?, ?, ?, ?, ?, ?)
18:26:32.906 [http-nio-8080-exec-2] DEBUG o.h.e.j.b.internal.AbstractBatchImpl - Reusing batch statement
18:26:32.906 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - insert into deal (amount, deal_id, timestamp, from_currency, source_file, to_currency, id) values (?, ?, ?, ?, ?, ?, ?)
18:26:32.906 [http-nio-8080-exec-2] 

... ...

DEBUG o.h.e.j.b.internal.AbstractBatchImpl - Reusing batch statement
18:26:34.002 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - insert into deal (amount, deal_id, timestamp, from_currency, source_file, to_currency, id) values (?, ?, ?, ?, ?, ?, ?)
18:26:34.002 [http-nio-8080-exec-2] DEBUG o.h.e.j.b.internal.AbstractBatchImpl - Reusing batch statement
18:26:34.002 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - insert into deal (amount, deal_id, timestamp, from_currency, source_file, to_currency, id) values (?, ?, ?, ?, ?, ?, ?)
18:26:34.002 [http-nio-8080-exec-2] DEBUG o.h.e.j.b.internal.AbstractBatchImpl - Reusing batch statement
18:26:34.002 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - insert into deal (amount, deal_id, timestamp, from_currency, source_file, to_currency, id) values (?, ?, ?, ?, ?, ?, ?)
18:26:34.002 [http-nio-8080-exec-2] DEBUG o.h.e.j.b.internal.AbstractBatchImpl - Reusing batch statement
18:26:34.002 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - insert into deal (amount, deal_id, timestamp, from_currency, source_file, to_currency, id) values (?, ?, ?, ?, ?, ?, ?)
18:26:34.002 [http-nio-8080-exec-2] DEBUG o.h.e.j.batch.internal.BatchingBatch - Executing batch size: 27
18:26:34.011 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - update deal_source_file set invalid_rows=?, source_file=?, valid_rows=? where id=?
18:26:34.015 [http-nio-8080-exec-2] DEBUG o.h.e.j.batch.internal.BatchingBatch - Executing batch size: 1
18:26:34.018 [http-nio-8080-exec-2] DEBUG o.h.e.t.i.jdbc.JdbcTransaction - committed JDBC Connection
18:26:34.018 [http-nio-8080-exec-2] DEBUG o.h.e.t.i.jdbc.JdbcTransaction - re-enabling autocommit
18:26:34.032 [http-nio-8080-exec-2] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@2354fb09] after transaction
18:26:34.032 [http-nio-8080-exec-2] DEBUG o.s.o.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager
18:26:34.032 [http-nio-8080-exec-2] DEBUG o.h.e.j.internal.JdbcCoordinatorImpl - HHH000420: Closing un-released batch
18:26:34.032 [http-nio-8080-exec-2] DEBUG o.h.e.j.i.LogicalConnectionImpl - Releasing JDBC connection
18:26:34.033 [http-nio-8080-exec-2] DEBUG o.h.e.j.i.LogicalConnectionImpl - Released JDBC connection

'

4 ответов


попробовав все возможные решения, я, наконец, нашел решение вставить 100 000 строк за 5 секунд!

вещи, которые я пробовал:

1) заменен hibernate / базы данных AUTOINCREMENT / сгенерированный id на self generated ID с помощью AtomicInteger

2) включение batch_inserts с batch_size=50

3) Очистка кэша после каждого "batch_size" количество вызовов persist ()

4) многопоточность (не пытался один)

наконец мы решили с помощью собственный запрос multi-insert и вставка 1000 строк в один запрос SQL insert вместо использования persist () на каждую сущность. Для вставки 100 000 объектов я создаю собственный запрос, подобный этому "INSERT into MyTable VALUES (x,x,x),(x,x,x).......(x,x,x)" [1000 вставок строк в один запрос SQL insert]

теперь требуется около 3 секунд для вставки 100 000 записей! Значит, узким местом был сам ОРМ! Для объемных вставок единственное, что, кажется, работает, это собственные запросы вставки!


  1. вы используете Spring для управления транзакцией, но нарушаете ее с помощью thread в качестве текущего контекста сеанса. При использовании Spring для управления транзакциями не возитесь с hibernate.current_session_context_class собственность. Удалить его.

  2. не используйте DriverManagerDataSource используйте правильный пул соединений, как HikariCP.

  3. в вашем цикле for вы должны flush и clear на EntityManager с регулярными интервалами, предпочтительно такими же, как ваша партия размер. Если вы не сохраняете ни одного, это занимает все больше и больше времени, потому что при этом Hibernate проверяет кэш первого уровня на наличие грязных объектов, чем больше объектов, тем больше времени это занимает. С 10 или 100 это приемлемо, но проверка 10000s объектов для каждого сохранится возьмет свое.

-

@Service
@Transactional
public class ServiceImpl implements MyService{

    @Autowired
    private MyDao dao;

    @PersistenceContext
    private EntityManager em;


    void foo(){
        int count = 0;
        for(MyObject d : listOfObjects_100000){
            dao.persist(d);
            count++;
            if ( (count % 30) == 0) {
               em.flush();
               em.clear();
            }    
        }
    }

для более глубокого объяснения см. этот блог и этот блог.


еще один вариант для рассмотрения -StatelessSession:

ориентированный на команды API для выполнения массовых операций против база данных.

сеанс без состояния не реализует кэш первого уровня и взаимодействуйте с любым кэшем второго уровня, и он не реализует транзакционная запись или автоматическая грязная проверка каскад операций для связанных экземпляров. Сборники игнорируются сеанс без состояния. Оперативный выполняется через сеанс без состояния обход модели событий и перехватчиков Hibernate. Сеансы без состояния уязвимы для эффектов сглаживания данных из-за отсутствия кэш первого уровня.

для некоторых видов транзакций сеанс без состояния может выполнять немного быстрее, чем сеанс со статусом.

обзоры обсуждение: использование StatelessSession для пакетной обработки


Uff. Вы можете сделать много вещей, чтобы увеличить скорость.

1.) Используйте @DynamicInsert и @DynamicUpdate, чтобы запретить БД вставлять непустые столбцы и обновлять измененные столбцы.

2.) Попробуйте вставить столбцы напрямую (без использования hibernate) в базу данных, чтобы увидеть, действительно ли hibernate является вашим узким местом.

3.) Используйте sessionfactory и только фиксируйте транзакцию каждые, например, 100 вставок. Или открывать и закрывать транзакцию только один раз и очистите данные каждые 100 вставок.

4.) Использовать стратегии ИД "секвенирования" и пусть зимуют выделить (через allocationsize параметр) идентификаторы.

5.) Используйте тайники.

некоторые из этих возможных решений могут иметь недостатки синхронизации при неправильном использовании. Но у тебя много возможностей.