Skip to content

Commit 57a0159

Browse files
authored
Improve JDBC insert performance (#2165)
* First naive implementation. * Refine implementation * Enable testDateTypes * remove jdbc v1 from tests * Use StatementType instead of boolean
1 parent ca3c0c0 commit 57a0159

File tree

3 files changed

+91
-8
lines changed

3 files changed

+91
-8
lines changed

jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@
3838
import java.time.format.DateTimeFormatter;
3939
import java.time.format.DateTimeFormatterBuilder;
4040
import java.time.temporal.ChronoField;
41+
import java.util.ArrayList;
4142
import java.util.Calendar;
4243
import java.util.Collection;
4344
import java.util.GregorianCalendar;
45+
import java.util.List;
4446
import java.util.Map;
4547

4648
public class PreparedStatementImpl extends StatementImpl implements PreparedStatement, JdbcV2Wrapper {
@@ -57,11 +59,19 @@ public class PreparedStatementImpl extends StatementImpl implements PreparedStat
5759
String originalSql;
5860
String [] sqlSegments;
5961
Object [] parameters;
62+
String insertIntoSQL;
63+
64+
StatementType statementType;
6065
public PreparedStatementImpl(ConnectionImpl connection, String sql) throws SQLException {
6166
super(connection);
62-
this.originalSql = sql;
67+
this.originalSql = sql.trim();
6368
//Split the sql string into an array of strings around question mark tokens
64-
this.sqlSegments = sql.split("\\?");
69+
this.sqlSegments = originalSql.split("\\?");
70+
this.statementType = parseStatementType(originalSql);
71+
72+
if (statementType == StatementType.INSERT) {
73+
insertIntoSQL = originalSql.substring(0, originalSql.indexOf("VALUES") + 6);
74+
}
6575

6676
//Create an array of objects to store the parameters
6777
if (originalSql.contains("?")) {
@@ -86,6 +96,19 @@ private String compileSql() {
8696
return sb.toString();
8797
}
8898

99+
private String valuesSql() {
100+
StringBuilder sb = new StringBuilder("(");
101+
for (int i = 0; i < parameters.length; i++) {
102+
if (i > 0) {
103+
sb.append(", ");
104+
}
105+
sb.append(parameters[i]);
106+
}
107+
sb.append(")");
108+
LOG.trace("Compiled Value SQL: {}", sb);
109+
return sb.toString();
110+
}
111+
89112
@Override
90113
public ResultSet executeQuery() throws SQLException {
91114
checkClosed();
@@ -228,7 +251,33 @@ public boolean execute() throws SQLException {
228251
@Override
229252
public void addBatch() throws SQLException {
230253
checkClosed();
231-
addBatch(compileSql());
254+
if (statementType == StatementType.INSERT) {
255+
addBatch(valuesSql());
256+
} else {
257+
addBatch(compileSql());
258+
}
259+
}
260+
261+
@Override
262+
public int[] executeBatch() throws SQLException {
263+
checkClosed();
264+
if (statementType == StatementType.INSERT && !batch.isEmpty()) {
265+
List<Integer> results = new ArrayList<>();
266+
// write insert into as batch to avoid multiple requests
267+
StringBuilder sb = new StringBuilder();
268+
sb.append(insertIntoSQL).append(" ");
269+
for (String sql : batch) {
270+
sb.append(sql).append(",");
271+
}
272+
sb.setCharAt(sb.length() - 1, ';');
273+
results.add(executeUpdate(sb.toString()));
274+
// clear batch and re-add insert into
275+
batch.clear();
276+
return results.stream().mapToInt(i -> i).toArray();
277+
} else {
278+
// run executeBatch
279+
return super.executeBatch();
280+
}
232281
}
233282

234283
@Override

jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,11 @@ public class StatementImpl implements Statement, JdbcV2Wrapper {
3333
protected boolean closed;
3434
private ResultSetImpl currentResultSet;
3535
private OperationMetrics metrics;
36-
private List<String> batch;
36+
protected List<String> batch;
3737
private String lastSql;
3838
private volatile String lastQueryId;
3939
private String schema;
4040
private int maxRows;
41-
4241
public StatementImpl(ConnectionImpl connection) throws SQLException {
4342
this.connection = connection;
4443
this.queryTimeout = 0;
@@ -460,11 +459,9 @@ public void clearBatch() throws SQLException {
460459
public int[] executeBatch() throws SQLException {
461460
checkClosed();
462461
List<Integer> results = new ArrayList<>();
463-
464-
for(String sql : batch) {
462+
for (String sql : batch) {
465463
results.add(executeUpdate(sql));
466464
}
467-
468465
return results.stream().mapToInt(i -> i).toArray();
469466
}
470467

jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.sql.Connection;
99
import java.sql.PreparedStatement;
1010
import java.sql.ResultSet;
11+
import java.sql.Statement;
1112
import java.sql.Timestamp;
1213
import java.sql.Types;
1314
import java.time.ZoneId;
@@ -276,4 +277,40 @@ void testWithClause() throws Exception {
276277
}
277278
assertEquals(count, 100);
278279
}
280+
281+
@Test(groups = { "integration" })
282+
void testInsert() throws Exception {
283+
int ROWS = 100000;
284+
try (Connection conn = getJdbcConnection()) {
285+
for (int j = 0; j < 10; j++) {
286+
try (Statement stmt = conn.createStatement()) {
287+
stmt.execute("CREATE TABLE insert_batch (`id` UInt32, `name` String) ENGINE = Memory");
288+
}
289+
String insertQuery = "INSERT INTO insert_batch (id, name) VALUES (?,?)";
290+
try (PreparedStatement stmt = conn.prepareStatement(insertQuery)) {
291+
for (int i = 0; i < ROWS; i++) {
292+
stmt.setInt(1, i);
293+
stmt.setString(2, "name" + i);
294+
stmt.addBatch();
295+
}
296+
long startBatchTime = System.currentTimeMillis();
297+
stmt.executeBatch();
298+
long endBatchTime = System.currentTimeMillis();
299+
System.out.println("Insertion Time for Final Batch: " + (endBatchTime - startBatchTime) + " ms");
300+
}
301+
// count rows
302+
try (Statement stmt = conn.createStatement()) {
303+
try (ResultSet rs = stmt.executeQuery("SELECT count(*) FROM insert_batch")) {
304+
assertTrue(rs.next());
305+
assertEquals(rs.getInt(1), ROWS);
306+
}
307+
}
308+
try (Statement stmt = conn.createStatement()) {
309+
stmt.execute("DROP TABLE insert_batch");
310+
}
311+
312+
}
313+
314+
}
315+
}
279316
}

0 commit comments

Comments
 (0)