web-dev-qa-db-fra.com

Besoin d'insérer 100000 lignes dans mysql en utilisant la mise en veille prolongée en moins de 5 secondes

J'essaie d'insérer 100 000 lignes dans une table MYSQL en moins de 5 secondes en utilisant Hibernate (JPA). J'ai essayé toutes les astuces en hibernation et je ne peux toujours pas faire mieux que 35 secondes.

1ère optimisation: j'ai commencé avec le générateur de séquence IDENTITY qui entraînait 60 secondes d'insertion. Plus tard, j'ai abandonné le générateur de séquences et j'ai commencé à affecter moi-même le champ @Id En lisant la fonction MAX(id) et en utilisant AtomicInteger.incrementAndGet() pour attribuer moi-même les champs. Cela a réduit le temps d'insertion à 35 secondes.

2ème optimisation: j'ai activé les insertions par lots, en ajoutant

<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>

à la configuration. J'ai été choqué de constater que les insertions par lots ne faisaient absolument rien pour réduire le temps d'insertion. C'était encore 35 secondes!

Maintenant, je pense à essayer d'insérer en utilisant plusieurs threads. Quelqu'un a des pointeurs? Aurais-je dû choisir MongoDB?

Voici ma configuration: 1. Configuration de mise en veille prolongée `

<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. Configuration de l'entité:

"

@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. Code persistant (service):

"

@Service
@Transactional
public class ServiceImpl implements MyService{

    @Autowired
    private MyDao dao;
....

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

"4. Classe Dao:

"

@Repository
public class DaoImpl implements MyDao{

    @PersistenceContext
    private EntityManager em;

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

"

Journaux: `

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

"

13
Kumar Manish

Après avoir essayé toutes les solutions possibles, j'ai finalement trouvé une solution pour insérer 100 000 lignes en moins de 5 secondes!

Choses que j'ai essayées:

1) Remplacer les identifiants AUTOINCREMENT/GENERATED de l'hibernation/base de données par des identifiants auto-générés à l'aide d'AtomicInteger

2) Activation de batch_inserts avec batch_size = 50

3) Vider le cache après chaque nombre 'batch_size' d'appels persist ()

4) multithreading (n'a pas essayé celui-ci)

Enfin, ce qui a fonctionné était d'utiliser une requête multi-insert native et d'insérer 1000 lignes dans une requête d'insertion sql au lieu d'utiliser persist () sur chaque entité. Pour insérer 100 000 entités, je crée une requête native comme celle-ci "INSERT into MyTable VALUES (x,x,x),(x,x,x).......(x,x,x)" [1 000 insertions de ligne dans une requête d'insertion SQL]

Il faut maintenant environ 3 secondes pour insérer 100 000 enregistrements! Le goulot d'étranglement était donc l'orm lui-même! Pour les insertions en masse, la seule chose qui semble fonctionner est les requêtes d'insertion natives!

12
Kumar Manish
  1. Vous utilisez Spring pour gérer la transaction mais interrompez-la en utilisant thread comme contexte de session en cours. Lorsque vous utilisez Spring pour gérer vos transactions, ne plaisante pas avec le hibernate.current_session_context_class propriété. Retirez-le.

  2. N'utilisez pas le DriverManagerDataSource utilisez un pool de connexions approprié comme HikariCP.

  3. Dans votre boucle for, vous devez flush et clear le EntityManager à intervalles réguliers, de préférence la même que la taille de votre lot. Si vous ne persistez pas, cela prend de plus en plus longtemps, car lorsque vous faites cela, Hibernate vérifie le cache de premier niveau pour les objets sales, plus il y a d'objets, plus cela prend de temps. Avec 10 ou 100, c'est acceptable, mais la vérification de 10 000 objets pour chaque persistance aura des conséquences.

-

@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();
            }    
        }
    }

Pour une explication plus approfondie, voir ce blog et ce blog .

2
M. Deinum

Une autre option à considérer est StatelessSession :

Une API orientée commande pour effectuer des opérations en masse sur une base de données.

Une session sans état n'implémente pas de cache de premier niveau, n'interagit pas avec un cache de deuxième niveau, n'implémente pas non plus d'écriture différée transactionnelle ou de vérification automatique sale, ni d'opérations en cascade vers les instances associées. Les collections sont ignorées par une session sans état. Les opérations effectuées via une session sans état contournent le modèle d'événement et les intercepteurs d'Hibernate. Les sessions sans état sont vulnérables aux effets d'alias de données, en raison de l'absence de cache de premier niveau.

Pour certains types de transactions, une session sans état peut fonctionner légèrement plus rapidement qu'une session avec état.

Discussion connexe: tilisation de StatelessSession pour le traitement par lots

2
Justas