【发布时间】:2017-03-03 19:35:51
【问题描述】:
我正在尝试以小批量(每个 csv 中的 6000 行)将大数据加载到 PostgreSQL 服务器中的一个表中(总共 4000 万行)。我认为 HikariCP 非常适合此目的。
这是我从数据插入中获得的吞吐量 Java 8 (1.8.0_65)、Postgres JDBC 驱动程序 9.4.1211 和 HikariCP 2.4.3。
6000 行在 4 分 42 秒内。
我做错了什么,如何提高插入速度?
关于我的设置的更多信息:
- 程序在公司网络后面的笔记本电脑上运行。
- Postgres 服务器 9.4 是带有 db.m4.large 和 50 GB SSD 的 Amazon RDS。
- 尚未在表上创建明确的索引或主键。
-
程序用大线程池异步插入每一行来保存请求,如下所示:
private static ExecutorService executorService = new ThreadPoolExecutor(5, 1000, 30L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(100000));
数据源配置为:
private DataSource getDataSource() {
if (datasource == null) {
LOG.info("Establishing dataSource");
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername(userName);
config.setPassword(password);
config.setMaximumPoolSize(600);// M4.large 648 connections tops
config.setAutoCommit(true); //I tried autoCommit=false and manually committed every 1000 rows but it only increased 2 minute and half for 6000 rows
config.addDataSourceProperty("dataSourceClassName","org.postgresql.ds.PGSimpleDataSource");
config.addDataSourceProperty("dataSource.logWriter", new PrintWriter(System.out));
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "1000");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
config.setConnectionTimeout(1000);
datasource = new HikariDataSource(config);
}
return datasource;
}
这是我读取源数据的地方:
private void readMetadata(String inputMetadata, String source) {
BufferedReader br = null;
FileReader fr = null;
try {
br = new BufferedReader(new FileReader(inputMetadata));
String sCurrentLine = br.readLine();// skip header;
if (!sCurrentLine.startsWith("xxx") && !sCurrentLine.startsWith("yyy")) {
callAsyncInsert(sCurrentLine, source);
}
while ((sCurrentLine = br.readLine()) != null) {
callAsyncInsert(sCurrentLine, source);
}
} catch (IOException e) {
LOG.error(ExceptionUtils.getStackTrace(e));
} finally {
try {
if (br != null)
br.close();
if (fr != null)
fr.close();
} catch (IOException ex) {
LOG.error(ExceptionUtils.getStackTrace(ex));
}
}
}
我正在异步插入数据(或尝试使用 jdbc!):
private void callAsyncInsert(final String line, String source) {
Future<?> future = executorService.submit(new Runnable() {
public void run() {
try {
dataLoader.insertRow(line, source);
} catch (SQLException e) {
LOG.error(ExceptionUtils.getStackTrace(e));
try {
errorBufferedWriter.write(line);
errorBufferedWriter.newLine();
errorBufferedWriter.flush();
} catch (IOException e1) {
LOG.error(ExceptionUtils.getStackTrace(e1));
}
}
}
});
try {
if (future.get() != null) {
LOG.info("$$$$$$$$" + future.get().getClass().getName());
}
} catch (InterruptedException e) {
LOG.error(ExceptionUtils.getStackTrace(e));
} catch (ExecutionException e) {
LOG.error(ExceptionUtils.getStackTrace(e));
}
}
我的 DataLoader.insertRow 如下:
public void insertRow(String row, String source) throws SQLException {
String[] splits = getRowStrings(row);
Connection conn = null;
PreparedStatement preparedStatement = null;
try {
if (splits.length == 15) {
String ... = splits[0];
//blah blah blah
String insertTableSQL = "insert into xyz(...) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ";
conn = getConnection();
preparedStatement = conn.prepareStatement(insertTableSQL);
preparedStatement.setString(1, column1);
//blah blah blah
preparedStatement.executeUpdate();
counter.incrementAndGet();
//if (counter.get() % 1000 == 0) {
//conn.commit();
//}
} else {
LOG.error("Invalid row:" + row);
}
} finally {
/*if (conn != null) {
conn.close(); //Do preparedStatement.close(); rather connection.close
}*/
if (preparedStatement != null) {
preparedStatement.close();
}
}
}
在 pgAdmin4 中进行监控时,我注意到一些事情:
- 每秒最高事务数接近 50。
- 活动数据库会话只有 1 个,会话总数为 15 个。
- 块 I/O 太多(达到 500 左右,不确定这是否值得关注)
【问题讨论】:
-
减少连接池的大小和使用的线程数:更多的连接(和更多的线程)并不一定会带来更好的性能,甚至有一点(这可能远低于你当前的设置),其中更多的连接(和线程)实际上会导致性能和吞吐量的降低。此外,您应该在您的方法中关闭连接,将其返回到连接池以供重用。
-
另外,您是否真的检查过瓶颈是否与异步插入有关,也许问题在于您未显示的代码(调用
callAsyncInsert)。 -
感谢您的回复:
-
将连接池和线程数减少到 10。此外,插入后关闭连接(关闭 ConnectionProxy 对象)。我调用 callAsyncInsert 并没有太多繁重的工作,只需读取 csv 并将其传递给 callAsyncInsert。进行这些更改后,仍然是 4 分 42 秒。有什么想法吗?
-
您可以尝试不异步执行,而是使用批量插入?显示调用
callAsyncInsert的代码?还要了解从您的笔记本电脑连接到托管在 AWS 上的数据库可能会有相当长的延迟。您是否针对本地数据库对此进行了测试?
标签: postgresql jdbc hikaricp