web-dev-qa-db-fra.com

Méthode efficace pour effectuer des insertions par lots avec JDBC

Dans mon application, je dois faire beaucoup d'inserts. C'est une application Java et j'utilise JDBC en clair pour exécuter les requêtes. La base de données étant Oracle. J'ai cependant activé le traitement par lots, de sorte que cela me permet d'économiser les temps de latence du réseau pour l'exécution de requêtes. Mais les requêtes s'exécutent en série en tant qu'INSERT distincts:

insert into some_table (col1, col2) values (val1, val2)
insert into some_table (col1, col2) values (val3, val4)
insert into some_table (col1, col2) values (val5, val6)

Je me demandais si la forme suivante de INSERT pourrait être plus efficace:

insert into some_table (col1, col2) values (val1, val2), (val3, val4), (val5, val6)

c'est-à-dire rassembler plusieurs INSERT en un seul.

D'autres astuces pour faire des INSERT de lot plus rapidement?

53
Aayush Puri

Ceci est un mélange des deux réponses précédentes:

  PreparedStatement ps = c.prepareStatement("INSERT INTO employees VALUES (?, ?)");

  ps.setString(1, "John");
  ps.setString(2,"Doe");
  ps.addBatch();

  ps.clearParameters();
  ps.setString(1, "Dave");
  ps.setString(2,"Smith");
  ps.addBatch();

  ps.clearParameters();
  int[] results = ps.executeBatch();
108
Tusc

Bien que la question demande d’insérer efficacement dans Oracle à l’aide de JDBC, je joue actuellement avec DB2 (sur l’ordinateur central IBM), l’insertion conceptuelle serait similaire;

  • insérer un enregistrement à la fois

  • insertion d'un lot d'enregistrements (très efficace)

Voici les métriques

1) Insertion d'un enregistrement à la fois

public void writeWithCompileQuery(int records) {
    PreparedStatement statement;

    try {
        Connection connection = getDatabaseConnection();
        connection.setAutoCommit(true);

        String compiledQuery = "INSERT INTO TESTDB.EMPLOYEE(EMPNO, EMPNM, DEPT, RANK, USERNAME)" +
                " VALUES" + "(?, ?, ?, ?, ?)";
        statement = connection.prepareStatement(compiledQuery);

        long start = System.currentTimeMillis();

        for(int index = 1; index < records; index++) {
            statement.setInt(1, index);
            statement.setString(2, "emp number-"+index);
            statement.setInt(3, index);
            statement.setInt(4, index);
            statement.setString(5, "username");

            long startInternal = System.currentTimeMillis();
            statement.executeUpdate();
            System.out.println("each transaction time taken = " + (System.currentTimeMillis() - startInternal) + " ms");
        }

        long end = System.currentTimeMillis();
        System.out.println("total time taken = " + (end - start) + " ms");
        System.out.println("avg total time taken = " + (end - start)/ records + " ms");

        statement.close();
        connection.close();

    } catch (SQLException ex) {
        System.err.println("SQLException information");
        while (ex != null) {
            System.err.println("Error msg: " + ex.getMessage());
            ex = ex.getNextException();
        }
    }
}

Les métriques pour 100 transactions: 

each transaction time taken = 123 ms
each transaction time taken = 53 ms
each transaction time taken = 48 ms
each transaction time taken = 48 ms
each transaction time taken = 49 ms
each transaction time taken = 49 ms
...
..
.
each transaction time taken = 49 ms
each transaction time taken = 49 ms
total time taken = 4935 ms
avg total time taken = 49 ms

La première transaction prend environ 120-150ms qui est pour l'analyse de la requête puis l'exécution, les transactions suivantes prennent seulement environ 50ms. (Ce qui est toujours élevé, mais ma base de données est sur un serveur différent (je dois dépanner le réseau))

2) Avec insertion dans un lot (efficace) - réalisé par preparedStatement.executeBatch()

public int[] writeInABatchWithCompiledQuery(int records) {
    PreparedStatement preparedStatement;

    try {
        Connection connection = getDatabaseConnection();
        connection.setAutoCommit(true);

        String compiledQuery = "INSERT INTO TESTDB.EMPLOYEE(EMPNO, EMPNM, DEPT, RANK, USERNAME)" +
                " VALUES" + "(?, ?, ?, ?, ?)";
        preparedStatement = connection.prepareStatement(compiledQuery);

        for(int index = 1; index <= records; index++) {
            preparedStatement.setInt(1, index);
            preparedStatement.setString(2, "empo number-"+index);
            preparedStatement.setInt(3, index+100);
            preparedStatement.setInt(4, index+200);
            preparedStatement.setString(5, "usernames");
            preparedStatement.addBatch();
        }

        long start = System.currentTimeMillis();
        int[] inserted = preparedStatement.executeBatch();
        long end = System.currentTimeMillis();

        System.out.println("total time taken to insert the batch = " + (end - start) + " ms");
        System.out.println("total time taken = " + (end - start)/records + " s");

        preparedStatement.close();
        connection.close();

        return inserted;

    } catch (SQLException ex) {
        System.err.println("SQLException information");
        while (ex != null) {
            System.err.println("Error msg: " + ex.getMessage());
            ex = ex.getNextException();
        }
        throw new RuntimeException("Error");
    }
}

La métrique pour un lot de 100 transactions est 

total time taken to insert the batch = 127 ms

et pour 1000 transactions

total time taken to insert the batch = 341 ms

Ainsi, 100 transactions dans ~5000ms (avec un trxn à la fois) sont réduites à ~150ms (avec un lot de 100 enregistrements).

NOTE - Ignorez mon réseau qui est super lent, mais les valeurs de métriques seraient relatives.

17
prayagupd

La Statement vous donne l'option suivante:

Statement stmt = con.createStatement();

stmt.addBatch("INSERT INTO employees VALUES (1000, 'Joe Jones')");
stmt.addBatch("INSERT INTO departments VALUES (260, 'Shoe')");
stmt.addBatch("INSERT INTO emp_dept VALUES (1000, 260)");

// submit a batch of update commands for execution
int[] updateCounts = stmt.executeBatch();
6
Bozho

Évidemment, vous devrez évaluer les performances, mais si vous utilisez un état PreparedStatement plutôt qu’un état, l’émission de plusieurs insertions sera beaucoup plus rapide avec JDBC.

4
Burleigh Bear

Vous pouvez utiliser ce paramètre rewriteBatchedStatements pour rendre l'insertion de lot encore plus rapide.

vous pouvez lire ici sur le param: MySQL et JDBC avec rewriteBatchedStatements = true

1
Alex Stanovsky

Vous pouvez utiliser addBatch et executeBatch pour une insertion de lot en Java. Voir l'exemple: Insertion de lot en Java

0
user1454294

Que diriez-vous d'utiliser l'instruction INSERT ALL?

INSERT ALL

INTO table_name VALUES ()

INTO table_name VALUES ()

...

SELECT Statement;

Je me souviens que la dernière instruction select est obligatoire pour que cette demande aboutisse. Vous ne vous souvenez pas pourquoi… .. Vous pouvez également utiliser PreparedStatement à la place. beaucoup d'avantages! 

Farid

0
Farid

Dans mon code, je n'ai pas d'accès direct à l'état "préparé", je ne peux donc pas utiliser batch, je lui transmets simplement la requête et une liste de paramètres. L'astuce consiste toutefois à créer une instruction d'insertion de longueur variable et une liste de paramètres LinkedList. L'effet est identique à l'exemple précédent, avec une longueur de paramètre variable. Voir ci-dessous (vérification d'erreur omise) . En supposant que 'maTable' comporte 3 champs pouvant être mis à jour: f1, f2 et f3

String []args={"A","B","C", "X","Y","Z" }; // etc, input list of triplets
final String QUERY="INSERT INTO [myTable] (f1,f2,f3) values ";
LinkedList params=new LinkedList();
String comma="";
StringBuilder q=QUERY;
for(int nl=0; nl< args.length; nl+=3 ) { // args is a list of triplets values
    params.add(args[nl]);
    params.add(args[nl+1]);
    params.add(args[nl+2]);
    q.append(comma+"(?,?,?)");
    comma=",";
}      
int nr=insertIntoDB(q, params);

dans ma classe DBInterface, j'ai:

int insertIntoDB(String query, LinkedList <String>params) {
    preparedUPDStmt = connectionSQL.prepareStatement(query);
    int n=1;
    for(String x:params) {
        preparedUPDStmt.setString(n++, x);
    }
    int updates=preparedUPDStmt.executeUpdate();
    return updates;
}
0
user3211098