J'essaie de remplir un resultSet en Java avec environ 50 000 lignes de 10 colonnes .__, puis de les insérer dans une autre table à l'aide de la méthode batchExecute
de PreparedStatement
.
Pour accélérer le processus, j'ai effectué des recherches et constaté que lors de la lecture des données dans resultSet, fetchSize joue un rôle important.
Avoir un fetchSize très faible peut entraîner un nombre excessif de déplacements sur le serveur et un fetchSize très élevé peut bloquer les ressources réseau. C'est pourquoi j'ai un peu expérimenté et défini une taille optimale adaptée à mon infrastructure.
Je lis ce resultSet et crée des instructions d'insertion à insérer dans une autre table d'une base de données différente.
Quelque chose comme ça (juste un exemple, pas un code réel):
for (i=0 ; i<=50000 ; i++) {
statement.setString(1, "[email protected]");
statement.setLong(2, 1);
statement.addBatch();
}
statement.executeBatch();
Lors de la mise à jour en masse (50 000 lignes 10 colonnes), est-il préférable d'utiliser une variable ResultSet
pouvant être mise à jour ou une méthode PreparedStaement avec une exécution par lots?
Je vais répondre à vos questions à tour de rôle.
Cela peut varier avec chaque pilote JDBC, mais les quelques études que j'ai étudiées effectueront une itération sur chaque entrée de lot et enverront les arguments avec le descripteur d'instruction préparé à chaque fois à la base de données pour exécution. C'est-à-dire que, dans votre exemple ci-dessus, il y aurait 50 000 exécutions de l'instruction préparée avec 50 000 paires d'arguments, mais ces 50 000 étapes peuvent être effectuées dans une "boucle interne" de niveau inférieur, où les économies de temps interviennent Une analogie plutôt étirée, c'est comme abandonner le "mode utilisateur" pour passer en "mode noyau" et y exécuter toute la boucle d'exécution. Vous économisez le coût de la plongée dans et hors de ce mode de niveau inférieur pour chaque entrée de lot.
Vous l'avez défini implicitement ici en ajoutant 50 000 jeux d'arguments avant d'exécuter le lot via Statement#executeBatch()
. Une taille de lot de un est tout aussi valide.
Pensez à ouvrir une transaction explicitement avant l'insertion du lot, puis validez-la. Ne laissez pas la base de données ou le pilote JDBC imposer une limite de transaction autour de chaque étape d'insertion du lot. Vous pouvez contrôler la couche JDBC avec la méthode Connection#setAutoCommit(boolean)
. Commencez par déconnecter la connexion du mode auto-commit, puis remplissez vos lots, démarrez une transaction, exécutez le lot, puis validez la transaction via Connection#commit()
.
Ce conseil suppose que vos insertions ne seront pas en concurrence avec des écrivains simultanés et que ces limites de transaction vous donneront des valeurs suffisamment cohérentes lues à partir de vos tables source pour une utilisation dans les insertions. Si ce n'est pas le cas, privilégiez la correction à la vitesse.
ResultSet
ou PreparedStatement
pouvant être mis à jour avec une exécution par lots?Rien ne vaut les tests avec le pilote JDBC de votre choix, mais je m'attends à ce que ces derniers —PreparedStatement
et Statement#executeBatch()
gagnent ici. Le descripteur d'instruction peut avoir une liste associée ou un tableau d '"arguments de traitement par lots", chaque entrée constituant l'ensemble d'arguments fourni entre les appels à Statement#executeBatch()
et à Statement#addBatch()
(ou Statement#clearBatch()
). La liste s'allongera à chaque appel de addBatch()
et ne sera vidée que lorsque vous aurez appelé executeBatch()
. Par conséquent, l'instance Statement
agit réellement comme un tampon d'arguments; vous échangez de la mémoire pour plus de commodité (en utilisant l'instance Statement
à la place de votre propre tampon d'argument externe).
Là encore, vous devriez considérer ces réponses comme générales et spéculatives tant que nous ne discutons pas d’un pilote JDBC spécifique. La sophistication de chaque pilote varie en fonction des optimisations qu'il poursuit.
Le lot se fera "tout à la fois" - c'est ce que vous lui avez demandé de faire.
50.000 semble un peu gros pour tenter en un seul appel. Je le diviserais en plus petits morceaux de 1 000, comme ceci:
final int BATCH_SIZE = 1000;
for (int i = 0; i < DATA_SIZE; i++) {
statement.setString(1, "[email protected]");
statement.setLong(2, 1);
statement.addBatch();
if (i % BATCH_SIZE == BATCH_SIZE - 1)
statement.executeBatch();
}
if (DATA_SIZE % BATCH_SIZE != 0)
statement.executeBatch();
50 000 lignes ne devraient pas prendre plus de quelques secondes.
S'il ne s'agit que de données provenant d'une/plusieurs tables de la base de données à insérer dans cette table et qu'il n'y ait aucune intervention (modifications du jeu de résultats), appelez statement.executeUpdate(SQL)
pour exécuter INSERT-SELECT instruction, cela est plus rapide car il n'y a pas de frais généraux. Aucune donnée ne sort de la base de données et l'ensemble de l'opération se trouve sur la base de données, pas dans l'application.
La mise à jour en bloc non consignée ne vous donnera pas les performances améliorées que vous souhaitez, telles que vous les réalisez. Voir this