HikariCP的Postgresql性能问题
我正在尝试以小批量(每个csv中有6000行)将大数据加载到PostgreSQL server中的一个表中(总共4000万行)。我认为HikariCP是实现这一目标的理想选择 这是我使用 Java 8(1.8.0_65)、Postgres JDBC驱动程序9.4.1211和HikariCP 2.4.3 4分42秒内排6000行。 我做错了什么?如何提高插入速度? 关于我的设置,请多说几句:HikariCP的Postgresql性能问题,postgresql,jdbc,hikaricp,Postgresql,Jdbc,Hikaricp,我正在尝试以小批量(每个csv中有6000行)将大数据加载到PostgreSQL server中的一个表中(总共4000万行)。我认为HikariCP是实现这一目标的理想选择 这是我使用 Java 8(1.8.0_65)、Postgres JDBC驱动程序9.4.1211和HikariCP 2.4.3 4分42秒内排6000行。 我做错了什么?如何提高插入速度? 关于我的设置,请多说几句: 该程序在我的笔记本电脑上运行,支持公司网络 Postgres服务器9.4是Amazon RDS,带有db
- 该程序在我的笔记本电脑上运行,支持公司网络
- Postgres服务器9.4是Amazon RDS,带有db.m4.large和50 GB SSD
- 尚未在表上定义显式索引或主键
- 程序使用大线程池异步插入每一行,以容纳请求,如下所示:
这是我读取源数据的地方:private static ExecutorService executorService = new ThreadPoolExecutor(5, 1000, 30L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(100000));
我正在异步插入数据(或尝试使用jdbc!): 在pgAdmin4中进行监控时,我注意到了以下几点: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)); } } }
- 每秒的最高事务数接近50
- 活动数据库会话只有一个,会话总数为15个
- 块I/O太多(达到500左右,不确定是否会引起问题)
您绝对希望使用批处理插入,语句在循环之外准备,并且自动提交关闭。在伪代码中:PreparedStatement stmt = conn.prepareStatement("insert into xyz(...) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)") while ( <data> ) { stmt.setString(1, column1); //blah blah blah stmt.addBatch(); } stmt.executeBatch(); conn.commit();
即使是单个连接上的单个线程也应该能够每秒插入>5000行 更新:如果要对其进行多线程处理,则连接数应为数据库CPU核心数x1.5或2。处理线程的数量应该与之匹配,每个处理线程应该使用上面的模式处理一个CSV文件。但是,您可能会发现,同一个表中的许多并发插入会在数据库中产生太多的锁争用,在这种情况下,您需要减少处理线程的数量,直到找到最佳并发性 一个适当大小的池和并发性应该很容易达到>20K行/秒PreparedStatement stmt=conn.prepareStatement(“插入xyz(…)值(?,,,,,,,,,,,,,,,,,)) 而(){ stmt.setString(1,第1列); //废话废话 stmt.addBatch(); } stmt.executeBatch(); conn.commit();
另外,请升级到HikariCP v2.6.0。减少连接池的大小和使用的线程数:更多的连接(和更多的线程)并不一定会带来更好的性能,甚至有一点(可能远低于当前设置)需要更多的连接(和线程)实际上会导致性能和吞吐量的降低。此外,您还应该关闭方法中的连接,将其返回到连接池以供重用。此外,您是否确实检查了瓶颈是否与异步插入有关,可能问题在于您没有显示的代码(调用
)。感谢您的回复:将连接池和线程数减少到10。另外,插入后关闭连接(关闭ConnectionProxy对象)。我称之为callAsyncInsert的工作并不繁重,只需读取csv并将其传递给callAsyncInsert即可。进行这些更改后,仍需4分42秒。有什么想法吗?您可以尝试不异步执行,而是使用批插入?显示调用callasyncsert
的代码?还要了解,从笔记本电脑连接到AWS上托管的数据库可能会有相当大的延迟。您在本地数据库上测试过吗?多线程导入的线程数量不仅取决于服务器上的CPU数量,还取决于该服务器上的硬盘数量。@a_horse_,带有_no_名称,如果为true,则使用Amazon RDS,则无法知道该数量。确定。我已根据建议修改了程序。升级到2.6.0。添加了批插入并仅使用连接加载数据。现在我看到了两种不同类型的数据集之间的巨大差异。数据集#1是一个csv文件中的500K行(精确地说是499951)-00:02:08.670分钟。数据集#2在83个CSV文件中为498K,每6K行花费00:02:09.674分钟。所以我可以得到每秒3840ish的吞吐量。如果我没有宏日志、错误处理等沉重的框架,我可能会得到更多,但我对此感到高兴。非常感谢Woolridge先生的这个框架和Mark的帮助。callAsyncInsert
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)); } }
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(); } } }
PreparedStatement stmt = conn.prepareStatement("insert into xyz(...) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)") while ( <data> ) { stmt.setString(1, column1); //blah blah blah stmt.addBatch(); } stmt.executeBatch(); conn.commit();