web-dev-qa-db-fra.com

Performances des insertions par lots JDBC

J'ai besoin d'insérer quelques centaines de millions d'enregistrements dans la base de données mysql. Je l'insère par lot 1 million à la fois. Veuillez voir mon code ci-dessous. Cela semble lent. Existe-t-il un moyen de l'optimiser?

try {
        // Disable auto-commit
        connection.setAutoCommit(false);

        // Create a prepared statement
        String sql = "INSERT INTO mytable (xxx), VALUES(?)";
        PreparedStatement pstmt = connection.prepareStatement(sql);

        Object[] vals=set.toArray();
        for (int i=0; i<vals.length; i++) {
            pstmt.setString(1, vals[i].toString());
            pstmt.addBatch();
        }

        // Execute the batch
        int [] updateCounts = pstmt.executeBatch();
        System.out.append("inserted "+updateCounts.length);
62
user157195

J'ai eu un problème de performances similaire avec mysql et l'ai résolu en définissant les propriétés seServerPrepStmts et rewriteBatchedStatements dans l'URL de connexion.

Connection c = DriverManager.getConnection("jdbc:mysql://Host:3306/db?useServerPrepStmts=false&rewriteBatchedStatements=true", "username", "password");
163
Bertil Chapuis

Je voudrais développer la réponse de Bertil, car j'ai expérimenté les paramètres d'URL de connexion.

rewriteBatchedStatements=true Est le paramètre important. useServerPrepStmts est déjà faux par défaut, et même le remplacer par true ne fait pas beaucoup de différence en termes de performances d'insertion par lots.

Maintenant, je pense qu'il est temps d'écrire comment rewriteBatchedStatements=true Améliore les performances de manière si spectaculaire. Il le fait par rewriting of prepared statements for INSERT into multi-value inserts when executeBatch() ( Source ). Cela signifie qu'au lieu d'envoyer les instructions n INSERT suivantes au serveur mysql chaque fois que executeBatch() est appelée:

INSERT INTO X VALUES (A1,B1,C1)
INSERT INTO X VALUES (A2,B2,C2)
...
INSERT INTO X VALUES (An,Bn,Cn)

Il enverrait une seule instruction INSERT:

INSERT INTO X VALUES (A1,B1,C1),(A2,B2,C2),...,(An,Bn,Cn)

Vous pouvez l'observer en basculant sur la journalisation mysql (par SET global general_log = 1) Qui se connecterait dans un fichier à chaque instruction envoyée au serveur mysql.

54
Eran

Vous pouvez insérer plusieurs lignes avec une seule instruction d'insertion, faire quelques milliers à la fois peut accélérer considérablement les choses, c'est-à-dire au lieu de faire par exemple 3 insertions de la forme INSERT INTO tbl_name (a,b,c) VALUES(1,2,3);, vous faites INSERT INTO tbl_name (a,b,c) VALUES(1,2,3),(1,2,3),(1,2,3); (Ce pourrait être JDBC .addBatch () fait une optimisation similaire maintenant - bien que le mysql addBatch ait été entièrement non optimisé et juste émis requêtes individuelles de toute façon - je ne sais pas si c'est toujours le cas avec les pilotes récents)

Si vous avez vraiment besoin de vitesse, chargez vos données à partir d'un fichier séparé par des virgules avec LOAD DATA INFILE , nous obtenons environ 7 à 8 fois l'accélération par rapport à des dizaines de millions d'insertions.

12
nos

Si:

  1. C'est une nouvelle table, ou le montant à insérer est supérieur aux données déjà insérées
  2. Il y a des index sur la table
  3. Vous n'avez pas besoin d'autre accès à la table pendant l'insertion

Ensuite ALTER TABLE tbl_name DISABLE KEYS peut considérablement améliorer la vitesse de vos insertions. Lorsque vous avez terminé, exécutez ALTER TABLE tbl_name ENABLE KEYS pour commencer à construire les index, ce qui peut prendre un certain temps, mais pas aussi longtemps que pour chaque insertion.

4
Wrikken
try {
        // Disable auto-commit
        connection.setAutoCommit(false);
        int maxInsertBatch = 10000;     
        // Create a prepared statement
        String sql = "INSERT INTO mytable (xxx), VALUES(?)";
        PreparedStatement pstmt = connection.prepareStatement(sql);

        Object[] vals=set.toArray();
        int count = 1;
        for (int i=0; i<vals.length; i++) {
            pstmt.setString(1, vals[i].toString());
            pstmt.addBatch();
            if(count%maxInsertBatch == 0){
                 pstmt.executeBatch();
            }
            count++;
        }

        // Execute the batch
        pstmt.executeBatch();
        System.out.append("inserted "+count);
1
Hieu HoangChi

Vous pouvez essayer d'utiliser l'objet DDBulkLoad.

// Get a DDBulkLoad object
DDBulkLoad bulkLoad = DDBulkLoadFactory.getInstance(connection);
bulkLoad.setTableName(“mytable”);
bulkLoad.load(“data.csv”);
1
Lalith