关系型数据库批量取数,fetchSize相关设置调研,mysql、pg、sqlserver、oracle

为了支持对关系型数据库的批量取数,防止内存溢出;调研了mysql、postgresql、sqlserver、oracle数据库的fetchSize设置;

数据库连接和处理的代码大同小异,首先贴一份可运行代码;

@Slf4j
public class TestFetchMysql {

    private Connection connection;

    private String driver;
    private String url;
    private String userName;
    private String password;

    public TestFetchMysql(String driver, String url, String userName, String password) {
        this.driver = driver;
        this.url = url;
        this.userName = userName;
        this.password = password;
    }

    private static void close(ResultSet rs, Statement st, PreparedStatement ps, Connection conn) {
        try {
            if (rs != null) rs.close();
            if (st != null) st.close();
            if (ps != null) ps.close();
            if (conn != null) conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private synchronized Connection getConnection(){
        try {
            if(connection == null || connection.isClosed()){
                Class.forName(driver);
                connection = DriverManager.getConnection(url, userName, password);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }

    public ArrayNode query(String query){
        Connection connection = getConnection();
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        ArrayNode result = null;
        try {
            //preparedStatement = connection.prepareStatement(query, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
            preparedStatement = connection.prepareStatement(query);
            preparedStatement.setFetchSize(Integer.MIN_VALUE);
            //preparedStatement.setFetchDirection(ResultSet.FETCH_REVERSE);
            resultSet = preparedStatement.executeQuery();
            printMem();
            result = toJsonStr(resultSet);
            printMem();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            close(resultSet, null, preparedStatement, null);
        }
        log.info("查找sql [{}] {} 查询结果 {}", query, System.getProperty("line.separator"), result);
        return result;
    }

    public static ArrayNode toJsonStr(ResultSet resultSet) throws SQLException {
        ObjectMapper objectMapper = new ObjectMapper();
        ArrayNode arrayNode = objectMapper.createArrayNode();

        ResultSetMetaData metaData = resultSet.getMetaData();
        int columnCount = metaData.getColumnCount();

        int count = 0;
        int sum = 0;
        while(resultSet.next()){
            count++;
            ObjectNode objectNode = objectMapper.createObjectNode();
            for(int i = 1; i <= columnCount; i++){
                String columnName  = metaData.getColumnLabel(i);
                String value = resultSet.getString(columnName);
                objectNode.put(columnName, value);
            }
            arrayNode.add(objectNode);
            if(count % 100 == 0){
                sum++;
                log.info("100个结果集 {}", sum);
                arrayNode = objectMapper.createArrayNode();
            }
        }
        return arrayNode;
    }

    public static void printMem(){
        log.info("totalMemory:{}, maxMemory:{}, freeMemory:{}", Runtime.getRuntime().maxMemory(), Runtime.getRuntime().freeMemory(),  Runtime.getRuntime().totalMemory());
    }

    public static void main(String[] args) {
       
        TestFetchMysql mysqlClient = new TestFetchMysql(DbType.getDriver("MYSQL") , "jdbc:mysql://127.0.0.1:3306/test_fetch", "test", "123456");

        ArrayNode query = mysqlClient.query("select * from test");
        log.info("打印下行数:" + query.size());
    }
}

DbType配置类

public enum DbType {

    SQLSERVER("SQLSERVER", "com.microsoft.sqlserver.jdbc.SQLServerDriver"),
    POSTGRESQL("POSTGRESQL", "org.postgresql.Driver"),
    MYSQL("MYSQL", "com.mysql.jdbc.Driver"),
    ORACLE("ORACLE", "oracle.jdbc.driver.OracleDriver");

    private String name;
    private String driver;

    DbType(String name, String driver) {
        this.name = name;
        this.driver = driver;
    }

    public static String getDriver(String name){
        for(DbType dbType : DbType.values()){
            if(dbType.getName().equals(name)){
                return dbType.getDriver();
            }
        }
        return name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDriver() {
        return driver;
    }

    public void setDriver(String driver) {
        this.driver = driver;
    }

    public static void main(String[] args) {
      //System.out.println( getDriver("POSTGRESQL") );
    }

测试是否生效手段

①设置ide运行参数-Xms8m -Xmx8m -Dfile.encoding=UTF-8

②printMem方法打印运行内存

③注释toJsonStr方法的以下代码,不生效内存溢出的会在执行sql得到resultSet的时刻,注释掉会在拼接Json的时刻

        if(count % 100 == 0){
                sum++;
                log.info("100个结果集 {}", sum);
                arrayNode = objectMapper.createArrayNode();
            }

下面是结论,测试过程可以自己复现

1、Mysql fetchSize设置

mysql版本5.7,配置参数如下

    preparedStatement.setFetchSize(Integer.MIN_VALUE);

网上教程需要设置一下两个参数,实际发现不需要

connection.prepareStatement(query, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
preparedStatement.setFetchDirection(ResultSet.FETCH_REVERSE);

必须设置 preparedStatement.setFetchSize(Integer.MIN_VALUE);  设置setFetchSize =20 不起作用会内存溢出

2、postgresql fetchSize设置

pg版本9.2.24,配置参数如下,设置自动提交为false,不设置FetchSize的设置会无效;

fetchSize按照自己的需要设置大小,一般来说越大越快

connection.setAutoCommit(false);  
preparedStatement.setFetchSize(1000);
*     类似mysql的设置会报错 报错fetch size必须大于0 或者 操作要求可卷动的 ResultSet,但此 ResultSet 是 FORWARD_ONLY。
*     connection.prepareStatement(query, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
*     preparedStatement.setFetchDirection(ResultSet.FETCH_REVERSE);
*     preparedStatement.setFetchSize(Integer.MIN_VALUE)

3、SqlServer fetchSize设置

sqlserver版本2014 12.0,sqlserver可以设置fetchSize,但是fetchSize不生效,不过也不会内存溢出;

一次查询大量数据不会内存溢出,好像一次只会查询128条?底层待了解

4、Oracle fetchSize设置

oracle版本 11,只需要设置fetchSize即可,而且fetchSize生效,按需设置;

默认好像是一次拿10条数据,效率很慢;

preparedStatement.setFetchSize(1000);

ps:新建的表,查询报找不到异常;所以使用程序创建了一张表,然后插入1w条数据做的测试。

发布了62 篇原创文章 · 获赞 33 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/Zzhou1990/article/details/101198312