內存管理-分段加載查詢數據調研

目錄

 

1 背景

2 JDBC

2.1 jdbc協議

2.2 數據查詢-JDBC實現

2.2.1 Mysql

3 使用遊標分批獲取數據

3.2 Mysql&Doris

3.3 Kylin

4 Spring jdbcTemplates

8 結論

9 參考資料


1 背景

在處理天璇大查詢的過程中,遇到一些問題:

  • 哪些數據庫能夠實現分批加載(MySQL、Doris、Kylin)?

  • 非JDBC的如何實現

  • 在不同的實現模式(原生驅動和jdbcTemplates以及zebra)下,具體如何使用(哪些必要條件,哪些方式,原理是什麼)才能實現分批加載。

帶着這些疑惑,進行了如下的一些探查。

2 JDBC

2.1 jdbc協議

何爲jdbc,JDBC,是一種用於Java編程語言和多種數據庫連接的標準Java API

 

體現在代碼層面就是在java.sql包中的一些接口:

  • DriverManager:該類被用於管理數據庫驅動。當Java應用發送一個數據庫連接請求時,會伴隨一個對應數據庫的驅動連接子協議。這個協議會通過DriverManager發送到各個Driver,而第一個識別該協議的Dirver會在對應的數據庫和JDBC接口間建立連接。

  • Driver:驅動接口負責處理與數據庫服務器之間的通信。Java編程人員很少會直接使用到這個接口,這是用於DriverManager類在管理着Driver接口。

  • Connection:這個接口包含了所有連接數據庫相關的方法。一個connection對象會包含了和數據庫通信的上下文等信息。所有與數據庫相關的通信都且僅需要通過connection對象。

  • Statement:該接口負責將Java代碼中嵌入的SQL語句提交到數據庫,另外還有一些派生出的接口可以完成參數的傳遞和存儲過程的執行。

  • ResultSet:通過Statement對象執行的查詢語句的查詢結果將被通過ResultSet對象從數據庫取回。ResultSet對象可以像迭代器一樣去操作數據。

  • SQLException:這個類用來處理數據庫應用中出現的各種錯誤。

 

2.2 數據查詢-JDBC實現

2.2.1 Mysql

一般就是各數據庫廠商提供的驅動,接下來以MySQL爲例進行分析。

驅動包 mysql-connector-java,該驅動包就有上述的JDBC實現。

接下來我們從一個簡單的數據庫查詢過程來看該實現的過程。

        String driver= "com.mysql.jdbc.Driver";useCursorFetch=true&defaultFetchSize=50
        String url = "jdbc:mysql://host:port/database?useCursorFetch=true&defaultFetchSize=50";
        Connection con;
        String user = "xxx";
        String password = "yyy";
        try {
            Class.forName(driver);
            con = DriverManager.getConnection(url, user, password);
            Statement statement = con.createStatement();
//            Statement statement = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
            String sql = "select *  from dw_column";//我的表格叫persons
            ResultSet resultSet = statement.executeQuery(sql);

            while (resultSet.next()){
                resultSet.last();
                System.out.println("行數" + resultSet.getRow());
                System.out.println(resultSet.getString("column_name"));
                System.out.println("hah");
            }
            System.out.println("哈哈");
            resultSet.close();
            con.close();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
  1. Class.forName(driver);
    在MySQL的Driver類中有如下靜態代碼,實現了在加載類的同時將驅動注入到Manager中,也因此能夠使用DriverMangager獲取Connection。

     static {
            try {
                java.sql.DriverManager.registerDriver(new Driver());
            } catch (SQLException E) {
                throw new RuntimeException("Can't register driver!");
            }
        }

     

  2. Connection
    MySQL驅動獲取到的Connection實現是

  3. statement的實現是StatementImpl.java

     

3 使用遊標分批獲取數據

JDBC在提供了相關的設置,useCursorFetch、defaultFetchSize。

當然這些要生效必須在這resultSetType和resultSetConcurrency兩個配置項處在對應的模式

Connection.createStatement(int resultSetType,int resultSetConcurrency)

// todo 這兩個配置項的可選值和含義。

只有resultSetType=1003、resultSetConcurrency=1007時,fetchSize才能生效,生效時,result不能使用resultSet.last()方法,會提示“UNsupport operation”

非fetchSize模式下,數據會一次全部加載到ResultSet中,可以使用last。(利用這個可以獲取總行數)

 

具體的實現依賴於各數據庫廠商的驅動。

3.2 Mysql&Doris

MySQL和Doris在jdbc查詢方面一樣

對於MySQL而言,最終需要在StatementImpl中需要設置了defaultFetchSize屬性。

 

在StatementImpl中有如下構造函數,會從Connection中獲取這些配置參數

這些參數附在jdbcUrl上或者在代碼中設置都是可以的(設置Connection或者Statement都是可以的,最終都會體現在Statement)

 int defaultFetchSize = this.connection.getDefaultFetchSize();
            if (defaultFetchSize != 0) {
                this.setFetchSize(defaultFetchSize);
            }

在jdbc上附上以上兩個參數之後,使用2.2章節中的測試代碼測試後可以發現,

jdbc4ResultSet --> rowData 中有個fetchedRows屬性,這個就是分批取出來的數據,在沒有next的之後此屬性是null,當遊標往後移動的時候,此時fetchedRows就會有值。

 

3.3 Kylin

public AvaticaStatement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return super.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
    }

從Kylin jdbc jar包來看,Kylin是支持的分批獲取的。

還需要更加具體的測試

 

4 Spring jdbcTemplates

jdbcTemplates,底層也是調用驅動包的實現類,來創建Connection和Statement,只是再上面一層又封裝了一層。jdbcTemplates的主要目的是封裝了對查詢後數據的處理。

queryForList方法,內部是採用了fetchSize的模式,逐條轉化爲List的元素

queryForRowSet,對外輸出類似ResultSet sqlRowSet,但是sqlRowSet雖然內部封裝了個ResultSet,但是卻不是原始的ResultSet,而是逐條處理之後的ReusltSet,所以sqlRowSet是全量數據集。

在滿足必要條件和配置了fetchSize後,jdbcTemplates諸多方法的內部是分批獲取數據的,只是jdbcTemplates對外提供都是整個結果集。

調用鏈如下所示:

SqlRowSetResultSetExtractor
	createSqlRowSet
		ResultSetWrappingSqlRowSet
			CachedRowSetImpl.populate(rs); // 該方法會逐條處理ResultSet。
      	 while(var1.next()) 。。。

 

8 結論

  1. MySQL數據源原生支持分批加載到JVM,必須滿足resultSetType=1003、resultSetConcurrency=1007條件,然後配置useCursorFetch=true&defaultFetchSize=50即可。

  2. spring 的jdbcTemplates提供的方法返回的都是含有所有數據的結果集,雖然其內部是分批獲取,但對外是一次性的提供數據

  3. 【TODO】其他類型的數據源待繼續調研

 

9 參考資料

https://my.oschina.net/yibuliushen/blog/887509

https://www.cnblogs.com/benwu/articles/9126972.html

https://blog.csdn.net/shb_derek1/article/details/8105935

https://zhuanlan.zhihu.com/p/47390514

https://my.oschina.net/liuyuanyuangogo/blog/330196

發佈了87 篇原創文章 · 獲贊 20 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章