Производительность пакетной вставки JDBC
Мне нужно вставить пару сотен миллионов записей в mysql db. Я участвую в выпуске 1 миллион за один раз. См. Мой код ниже. Кажется, он медленный. Есть ли способ его оптимизировать?
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);
Ответы
Ответ 1
У меня была аналогичная проблема с производительностью с mysql и она была решена путем установки свойств useServerPrepStmts и rewriteBatchedStatements в URL-адресе подключения.
Connection c = DriverManager.getConnection("jdbc:mysql://host:3306/db?useServerPrepStmts=false&rewriteBatchedStatements=true", "username", "password");
Ответ 2
Я хотел бы расширить ответ на Bertil, поскольку я экспериментировал с параметрами URL-адреса подключения.
rewriteBatchedStatements=true
является важным параметром. useServerPrepStmts
по умолчанию уже ложно, и даже изменение его на true не имеет большого значения в плане производительности пакетной вставки.
Теперь я думаю, что настало время написать, как rewriteBatchedStatements=true
значительно улучшает производительность. Он делает это через rewriting of prepared statements for INSERT into multi-value inserts when executeBatch()
(Источник). Это означает, что вместо отправки следующих операторов n
INSERT на сервер mysql каждый раз вызывается executeBatch()
:
INSERT INTO X VALUES (A1,B1,C1)
INSERT INTO X VALUES (A2,B2,C2)
...
INSERT INTO X VALUES (An,Bn,Cn)
Он отправил бы один оператор INSERT:
INSERT INTO X VALUES (A1,B1,C1),(A2,B2,C2),...,(An,Bn,Cn)
Вы можете наблюдать за ним, переключаясь на журнал mysql (через SET global general_log = 1
), который будет записывать в файл каждый оператор, отправленный на сервер mysql.
Ответ 3
Вы можете вставлять несколько строк в один оператор insert, делая несколько тысяч за раз, может значительно ускорить процесс, то есть вместо того, чтобы делать, например, 3 вставки формы INSERT INTO tbl_name (a,b,c) VALUES(1,2,3);
, вы делаете INSERT INTO tbl_name (a,b,c) VALUES(1,2,3),(1,2,3),(1,2,3);
(может быть, JDBC.addBatch() делает подобную оптимизацию сейчас - хотя mysql addBatch раньше не оптимизировался и просто выдавал отдельные запросы в любом случае - я не знаете, если это все еще имеет место с недавними драйверами)
Если вам действительно нужна скорость, загрузите свои данные из файла с разделителями-запятыми с помощью LOAD DATA INFILE, мы получаем около 7-8 раз ускорение что против десятков миллионов вставок.
Ответ 4
Если:
- Это новая таблица или сумма, которая будет вставлена, больше, чем уже вставленные данные
- В таблице есть индексы
- Вам не нужен другой доступ к таблице во время вставки
Затем ALTER TABLE tbl_name DISABLE KEYS
может значительно улучшить скорость ваших вставок. Когда вы закончите, запустите ALTER TABLE tbl_name ENABLE KEYS
, чтобы начать создавать индексы, что может занять некоторое время, но не так долго, как делать это для каждой вставки.
Ответ 5
Вы можете попробовать использовать объект DDBulkLoad.
// Get a DDBulkLoad object
DDBulkLoad bulkLoad = DDBulkLoadFactory.getInstance(connection);
bulkLoad.setTableName("mytable");
bulkLoad.load("data.csv");
Ответ 6
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);