JDBC postgresql大数据量流式读取

2024-06-02 17:18

本文主要是介绍JDBC postgresql大数据量流式读取,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 

前言:

最近做数据同步,需要从PostgreSql获取数据,发现一旦数据比较多,那么读取的速度非常慢,并且内存占用特别多&GC不掉。

代码样例:

为了方便讲解,下面写了事例代码,从b2c_order获取数据,这个数据表6G左右。

 

package com.synchro;import java.sql.*;/*** Created by qiu.li on 2015/10/16.*/
public class Test {public static void main(String[] args) {Connection conn = null;try {Class.forName("org.postgresql.Driver");conn = DriverManager.getConnection("jdbc:postgresql://***.qunar.com:5432/database", "username", "password");String sql = "select * from mirror.b2c_order";PreparedStatement ps = conn.prepareStatement(sql);ResultSet rs = ps.executeQuery();int i = 0;while (rs.next()) {i++;if (i % 100 == 0) {System.out.println(i);}}} catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException e) {e.printStackTrace();}}
}

 

现象:

在Idea执行代码,发现卡死,并且占用大量的内存

解决方案:

然后我决定开始逐步调试,跟踪代码:

第一步、我发现是在执行executeQuery方法的时候卡住的

第二步、是在执行AbstractJdbc2Statement.executeWithFlags方法卡住的

第三步、继续跟踪,并在网络上查看可能引起的原因是和设置fetchSize参数相关,所以我设置了fetchSize,奇葩的是没有生效

第四步、sendQuery,sendOneQuery方法,在这里发现了问题,好在代码不太多,我就都贴出来了:

 

        boolean usePortal = (flags & 8) != 0 && !noResults && !noMeta && fetchSize > 0 && !describeOnly;boolean oneShot = (flags & 1) != 0 && !usePortal;int rows;if(noResults) {rows = 1;} else if(!usePortal) {rows = maxRows;} else if(maxRows != 0 && fetchSize > maxRows) {rows = maxRows;} else {rows = fetchSize;}

 

可见是usePortal是true,那么fetchSize才会生效。

boolean usePortal = (flags & 8) != 0 && !noResults && !noMeta && fetchSize > 0 && !describeOnly;

那么咱们逐一看一下这些条件:

  • !noResults表示这个SQL不需要返回任何结果,这个肯定等于true,因为所有的select都会要求返回结果
  • !noMeta表示这个SQL不需要返回元数据,这个肯定等于true,因为select都要求返回元数据,供后续的resultSet.get使用
  • !fetchSize大于0,这个不说了,自然是true
  • !describeOnly,这个只有在desc table这样的语句的时候,才会是false,对于select,也是true

那么,试下的唯一的可能导致usePortal为true的原因就是 flags & 8这个值是true。。(我想说这种写法很别致,tmd,设置flags的时候肯定是flags=flag|8,后来发现新的驱动修改了这种写法)

继续往上翻,看看什么时候才会执行flags = flags | 8 这个代码了,因为只有这个代码被执行过,才会导致上面这个条件为true

        if(this.fetchSize > 0 && !this.wantsScrollableResultSet() && !this.connection.getAutoCommit() && !this.wantsHoldableResultSet()) {flags |= 8;}

其中:wantsHoldableResultSet()代码直接返回的false,所以,不考虑这个。

那么,wantsScrollableResultSet()返回false,并且connection.getAutoCommit()返回false,才会导致fetchSize生效。wantsScrollableResultSet()这个方法的代码为:

protected boolean wantsScrollableResultSet() {return resultsettype != 1003; //老代码,看到这里我真想死,1003是啥?好在偶然的机会看见了新的Postgresql驱动,使用ResultSet.TYPE_FORWARD_ONLY表示1003
}

至此,问题终于被定位:

1、如果connection不是自动提交事务的,那么,fetchSize将生效(非默认)

2、如果statement是TYPE_FORWARD_ONLY的,那么,fetchSize也将生效(默认)

结论

如果想fetchSize生效,必须保证connection是autocommit = false的,并且,statement为1003(forward_only)的:

conn.setAutoCommit(false);
final Statement statement = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.FETCH_FORWARD);

另外,不带参数的conn.createStatement(),其默认就是TYPE_FORWARD_ONLY。所以,一般情况下,如果想fetchsize生效,只须设置autocommit为flase,也就是需要手工去管理事务。默认的源代码如下:

    public Statement createStatement() throws SQLException {return this.createStatement(1003, 1007); //有兴趣的同学可以继续跟踪看看,1003就是resultsettype}

代码:

那么修改代码如下:

 

package com.synchro;import java.sql.*;/*** Created by qiu.li on 2015/10/16.*/
public class Test {public static void main(String[] args) {Connection conn = null;try {Class.forName("org.postgresql.Driver");conn = DriverManager.getConnection("jdbc:postgresql://***.qunar.com:5432/datasource", "username", "password");conn.setAutoCommit(false); //并不是所有数据库都适用,比如hive就不支持,orcle不需要String sql = "select * from mirror.b2c_order";PreparedStatement ps = conn.prepareStatement(sql);ps.setFetchSize(1000); //每次获取1万条记录//ps.setMaxRows(1000);ResultSet rs = ps.executeQuery();int i = 0;while (rs.next()) {i++;if (i % 100 == 0) {System.out.println(i);}}} catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException e) {e.printStackTrace();}}
}

 

这次再一次执行,发现根本不卡。

感悟:类似这种问题都的慢慢跟踪代码,更重要的是身边需要有同事可以相互讨论,形成氛围,因为这个过程十分乏味,自己很难坚持下来。

这篇关于JDBC postgresql大数据量流式读取的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1024589

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

MyBatis Plus大数据量查询慢原因分析及解决

《MyBatisPlus大数据量查询慢原因分析及解决》大数据量查询慢常因全表扫描、分页不当、索引缺失、内存占用高及ORM开销,优化措施包括分页查询、流式读取、SQL优化、批处理、多数据源、结果集二次... 目录大数据量查询慢的常见原因优化方案高级方案配置调优监控与诊断总结大数据量查询慢的常见原因MyBAT

C#实现SHP文件读取与地图显示的完整教程

《C#实现SHP文件读取与地图显示的完整教程》在地理信息系统(GIS)开发中,SHP文件是一种常见的矢量数据格式,本文将详细介绍如何使用C#读取SHP文件并实现地图显示功能,包括坐标转换、图形渲染、平... 目录概述功能特点核心代码解析1. 文件读取与初始化2. 坐标转换3. 图形绘制4. 地图交互功能缩放

使用Node.js和PostgreSQL构建数据库应用

《使用Node.js和PostgreSQL构建数据库应用》PostgreSQL是一个功能强大的开源关系型数据库,而Node.js是构建高效网络应用的理想平台,结合这两个技术,我们可以创建出色的数据驱动... 目录初始化项目与安装依赖建立数据库连接执行CRUD操作查询数据插入数据更新数据删除数据完整示例与最佳

java读取excel文件为base64实现方式

《java读取excel文件为base64实现方式》文章介绍使用ApachePOI和EasyExcel处理Excel文件并转换为Base64的方法,强调EasyExcel适合大文件且内存占用低,需注意... 目录使用 Apache POI 读取 Excel 并转换为 Base64使用 EasyExcel 处

MySQL的JDBC编程详解

《MySQL的JDBC编程详解》:本文主要介绍MySQL的JDBC编程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、前置知识1. 引入依赖2. 认识 url二、JDBC 操作流程1. JDBC 的写操作2. JDBC 的读操作总结前言本文介绍了mysq

PostgreSQL简介及实战应用

《PostgreSQL简介及实战应用》PostgreSQL是一种功能强大的开源关系型数据库管理系统,以其稳定性、高性能、扩展性和复杂查询能力在众多项目中得到广泛应用,本文将从基础概念讲起,逐步深入到高... 目录前言1. PostgreSQL基础1.1 PostgreSQL简介1.2 基础语法1.3 数据库

使用Java读取本地文件并转换为MultipartFile对象的方法

《使用Java读取本地文件并转换为MultipartFile对象的方法》在许多JavaWeb应用中,我们经常会遇到将本地文件上传至服务器或其他系统的需求,在这种场景下,MultipartFile对象非... 目录1. 基本需求2. 自定义 MultipartFile 类3. 实现代码4. 代码解析5. 自定

MySQL 数据库表操作完全指南:创建、读取、更新与删除实战

《MySQL数据库表操作完全指南:创建、读取、更新与删除实战》本文系统讲解MySQL表的增删查改(CURD)操作,涵盖创建、更新、查询、删除及插入查询结果,也是贯穿各类项目开发全流程的基础数据交互原... 目录mysql系列前言一、Create(创建)并插入数据1.1 单行数据 + 全列插入1.2 多行数据