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?
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();
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
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))
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.
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();
É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.
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
Vous pouvez utiliser addBatch et executeBatch pour une insertion de lot en Java. Voir l'exemple: Insertion de lot en Java
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
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;
}