亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Mysql中JDBC的三種查詢(普通、流式、游標)詳解

 更新時間:2023年08月04日 09:53:31   作者:趕路人兒  
這篇文章主要介紹了Mysql中JDBC的三種查詢(普通、流式、游標)詳解,JDBC(Java DataBase Connectivity:java數(shù)據(jù)庫連接)是一種用于執(zhí)行SQL語句的Java API,可以為多種關(guān)系型數(shù)據(jù)庫提供統(tǒng)一訪問,它是由一組用Java語言編寫的類和接口組成的,需要的朋友可以參考下

JDBC查詢

使用JDBC向mysql發(fā)送查詢時,有三種方式:

  1. 常規(guī)查詢:JDBC驅(qū)動會阻塞的一次性讀取全部查詢的數(shù)據(jù)到 JVM 內(nèi)存中,或者分頁讀取
  2. 流式查詢:每次執(zhí)行rs.next時會判斷數(shù)據(jù)是否需要從mysql服務(wù)器獲取,如果需要觸發(fā)讀取一批數(shù)據(jù)(可能n行)加載到 JVM 內(nèi)存進行業(yè)務(wù)處理
  3. 游標查詢:通過 fetchSize 參數(shù),控制每次從mysql服務(wù)器一次讀取多少行數(shù)據(jù)。

1、常規(guī)查詢

public static void normalQuery() throws SQLException {
    Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3307/test?useSSL=false", "root", "123456");
    PreparedStatement statement = connection.prepareStatement(sql);
    //statement.setFetchSize(100); //不起作用
    ResultSet resultSet = statement.executeQuery();
    while(resultSet.next()){
        System.out.println(resultSet.getString(2));
    }
    resultSet.close();
    statement.close();
    connection.close();
}

1)說明:

第四行設(shè)置featchSize不起作用。第五行statement.executeQuery()執(zhí)行查詢會阻塞,因為需要等到所有數(shù)據(jù)返回并放到內(nèi)存中;接下來每次執(zhí)行resultSet.next()方法會從內(nèi)存中獲取數(shù)據(jù)。

2)將jvm內(nèi)存設(shè)置較?。?Xms16m -Xmx16m),對于大數(shù)據(jù)的查詢會產(chǎn)生OOM:

為了避免OOM,通常我們會使用分頁查詢,或者下面的兩種方式。

2、流式查詢

public static void streamQuery() throws Exception { 
    Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3307/test?useSSL=false", "root", "123456");
    PreparedStatement statement = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);    
    statement.setFetchSize(Integer.MIN_VALUE); 
    //或者通過 com.mysql.jdbc.StatementImpl
    ((StatementImpl) statement).enableStreamingResults();
    ResultSet rs = statement.executeQuery();
    while (rs.next()) {
        System.out.println(rs.getString(2));
    }
    rs.close();
    statement.close();
    connection.close();
}

2.1 流式查詢的條件:

隨著大數(shù)據(jù)的到來,對于百萬、千萬的數(shù)據(jù)使用流式查詢可以有效避免OOM。

在執(zhí)行statement.executeQuery()時不會從TCP響應(yīng)流中讀取完所有數(shù)據(jù),當(dāng)下面執(zhí)行rs.next()時會按照需要從TCP響應(yīng)流中讀取部分數(shù)據(jù)。

  • 創(chuàng)建Statement的時候需要制定ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY
  • 設(shè)置fetchSize位Integer.MIN_VALUE

或者通過com.mysql.jdbc.StatementImpl的enableStreamingResults()方法設(shè)置。

二者是一致的。看mysql的jdbc(com.mysql.jdbc.StatementImpl)

源碼:

2.2 流式查詢原理:

1)基本概念

我們要知道jdbc客戶端和mysql服務(wù)器之間是通過TCP建立的通信,使用mysql協(xié)議進行傳輸數(shù)據(jù)。

首先聲明一個概念:在三次握手建立了TCP連接后,就可以在這個通道上進行通信了,直到關(guān)閉該連接。

在 TCP 中發(fā)送端和接收端**可以是客戶端/服務(wù)端,也可以是服務(wù)器/客戶端**,通信的雙方在任意時刻既可以是接收數(shù)據(jù)也可以是發(fā)送數(shù)據(jù)(全雙工)。

在通信中,收發(fā)雙方都不保持記錄的邊界,所以需要按照一定的協(xié)議進行表示。在mysql中會按照mysql協(xié)議來進行交互。

有了上面的概念,我們重新來定義這兩種查詢:

在執(zhí)行st.executeQuery()時,jdbc驅(qū)動會通過connection對象和mysql服務(wù)器建立TCP連接,同時在這個鏈接通道中發(fā)送sql命令,并接受返回。

二者的區(qū)別是:

  • 普通查詢:也叫批量查詢,jdbc客戶端會阻塞的一次性從TCP通道中讀取完mysql服務(wù)的返回數(shù)據(jù);
  • 流式查詢:分批的從TCP通道中讀取mysql服務(wù)返回的數(shù)據(jù),每次讀取的數(shù)據(jù)量并不是一行(通常是一個package大小),jdbc客戶端在調(diào)用rs.next()方法時會根據(jù)需要從TCP流通道中讀取部分數(shù)據(jù)。(并不是每次讀區(qū)一行數(shù)據(jù),網(wǎng)上說的幾乎都是錯的?。?/li>

2)源碼查看:

從statement.executeQuery()方法跟進去,主要的調(diào)用連如下:

protected ResultSetInternalMethods executeInternal(int maxRowsToRetrieve, Buffer sendPacket, boolean createStreamingResultSet, boolean queryIsSelectOnly,
            Field[] metadataFromCache, boolean isBatch) throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            MySQLConnection locallyScopedConnection = this.connection;
            rs = locallyScopedConnection.execSQL(this, null, maxRowsToRetrieve, sendPacket, this.resultSetType, this.resultSetConcurrency,
                            createStreamingResultSet, this.currentCatalog, metadataFromCache, isBatch);
            return rs;
        }
public ResultSetInternalMethods execSQL(StatementImpl callingStatement, String sql, int maxRows, Buffer packet, int resultSetType, int resultSetConcurrency,
            boolean streamResults, String catalog, Field[] cachedMetadata, boolean isBatch) throws SQLException {
        synchronized (getConnectionMutex()) {
            return this.io.sqlQueryDirect(callingStatement, null, null, packet, maxRows, resultSetType, resultSetConcurrency, streamResults, catalog,
                        cachedMetadata);
        }
}
final ResultSetInternalMethods sqlQueryDirect(StatementImpl callingStatement, String query, String characterEncoding, Buffer queryPacket, int maxRows,
            int resultSetType, int resultSetConcurrency, boolean streamResults, String catalog, Field[] cachedMetadata) throws Exception {
        Buffer resultPacket = sendCommand(MysqlDefs.QUERY, null, queryPacket, false, null, 0);
        ResultSetInternalMethods rs = readAllResults(callingStatement, maxRows, resultSetType, resultSetConcurrency, streamResults, catalog, resultPacket,
                    false, -1L, cachedMetadata);
        return rs;
}
ResultSetImpl readAllResults(StatementImpl callingStatement, int maxRows, int resultSetType, int resultSetConcurrency, boolean streamResults,
            String catalog, Buffer resultPacket, boolean isBinaryEncoded, long preSentColumnCount, Field[] metadataFromCache) throws SQLException {
        ResultSetImpl topLevelResultSet = readResultsForQueryOrUpdate(callingStatement, maxRows, resultSetType, resultSetConcurrency, streamResults, catalog,
                resultPacket, isBinaryEncoded, preSentColumnCount, metadataFromCache);
        return topLevelResultSet;
}
protected final ResultSetImpl readResultsForQueryOrUpdate(StatementImpl callingStatement, int maxRows, int resultSetType, int resultSetConcurrency,
            boolean streamResults, String catalog, Buffer resultPacket, boolean isBinaryEncoded, long preSentColumnCount, Field[] metadataFromCache) throws SQLException {
            com.mysql.jdbc.ResultSetImpl results = getResultSet(callingStatement, columnCount, maxRows, resultSetType, resultSetConcurrency, streamResults,
                    catalog, isBinaryEncoded, metadataFromCache);
            return results;
        }
}
protected ResultSetImpl getResultSet(StatementImpl callingStatement, long columnCount, int maxRows, int resultSetType, int resultSetConcurrency,
            boolean streamResults, String catalog, boolean isBinaryEncoded, Field[] metadataFromCache) throws SQLException {
        Buffer packet; // The packet from the server
        RowData rowData = null;
        if (!streamResults) {
            rowData = readSingleRowSet(columnCount, maxRows, resultSetConcurrency, isBinaryEncoded, (metadataFromCache == null) ? fields : metadataFromCache);
        } else {
            rowData = new RowDataDynamic(this, (int) columnCount, (metadataFromCache == null) ? fields : metadataFromCache, isBinaryEncoded);
            this.streamingData = rowData;
        }
        ResultSetImpl rs = buildResultSetWithRows(callingStatement, catalog, (metadataFromCache == null) ? fields : metadataFromCache, rowData, resultSetType,
                resultSetConcurrency, isBinaryEncoded);
        return rs;
}

說明:

  1. sqlQueryDirect()方法中的sendCommand會通過io發(fā)送sql命令請求到mysql服務(wù)器,并獲取返回流mysqlOutput
  2. getResultSet()方法會判斷是否是流式查詢還是批量查詢。MySQL驅(qū)動會根據(jù)不同的參數(shù)設(shè)置選擇對應(yīng)的ResultSet實現(xiàn)類,分別對應(yīng)三種查詢方式:
    1. RowDataStatic 靜態(tài)結(jié)果集,默認的查詢方式,普通查詢
    2. RowDataDynamic 動態(tài)結(jié)果集,流式查詢
    3. RowDataCursor 游標結(jié)果集,服務(wù)器端基于游標查詢

看上述代碼(41行),對于批量查詢:readSingleRowSet方法會循環(huán)掉用nextRow方法獲取所有數(shù)據(jù),然后放到j(luò)vm內(nèi)存的rows中:

對于流式查詢:直接創(chuàng)建RowDataDynamic對象返回。后面在掉用rs.next()獲取數(shù)據(jù)時會根據(jù)需要從mysqlOutput流中讀取數(shù)據(jù)。

2.3 流式查詢的坑:

public static void streamQuery2() throws Exception { 
    Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3307/test?useSSL=false", "root", "123456");
    //statement1
    PreparedStatement statement = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);    
    statement.setFetchSize(Integer.MIN_VALUE); 
    ResultSet rs = statement.executeQuery();
    if (rs.next()) {
        System.out.println(rs.getString(2));
    }
    //statement2
    PreparedStatement statement2 = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);    
    statement2.setFetchSize(Integer.MIN_VALUE); 
    ResultSet rs2 = statement2.executeQuery();
    if (rs2.next()) {
        System.out.println(rs2.getString(2));
    }
//      rs.close();
//      statement.close();
//      connection.close();
}

執(zhí)行結(jié)果:

test1
java.sql.SQLException: Streaming result set com.mysql.jdbc.RowDataDynamic@45c8e616 is still active. No statements may be issued when any streaming result sets are open and in use on a given connection. Ensure that you have called .close() on any active streaming result sets before attempting more queries.
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:869)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:865)
    at com.mysql.jdbc.MysqlIO.checkForOutstandingStreamingData(MysqlIO.java:3217)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2453)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2683)
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2482)
    at com.mysql.jdbc.StatementImpl.executeSimpleNonQuery(StatementImpl.java:1465)
    at com.mysql.jdbc.StatementImpl.setupStreamingTimeout(StatementImpl.java:726)
    at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:1939)
    at com.tencent.clue_disp_api.MysqlTest.streamQuery2(MysqlTest.java:79)
    at com.tencent.clue_disp_api.MysqlTest.main(MysqlTest.java:25)

MySQL Connector/J 5.1 Developer Guide中原文:

There are some caveats with this approach. You must read all of the rows in the result set (or close it) before you can issue any other queries on the connection, or an exception will be thrown. 也就是說當(dāng)通過流式查詢獲取一個ResultSet后,通過next迭代出所有元素之前或者調(diào)用close關(guān)閉它之前,不能使用同一個數(shù)據(jù)庫連接去發(fā)起另外一個查詢,否者拋出異常(第一次調(diào)用的正常,第二次的拋出異常)。

2.4 抓包驗證:

查看3307 > 62169的包可以發(fā)現(xiàn),ack都是1324,證明都是針對當(dāng)時sql請求的返回數(shù)據(jù)。

3、游標查詢

public static void cursorQuery() throws Exception {
    Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3307/test?useSSL=false&useCursorFetch=true", "root", "123456");
    ((JDBC4Connection) connection).setUseCursorFetch(true); //com.mysql.jdbc.JDBC4Connection
    Statement statement = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);    
    statement.setFetchSize(2);    
    ResultSet rs = statement.executeQuery(sql);    
    while (rs.next()) {
        System.out.println(rs.getString(2));
        Thread.sleep(5000);
    }
    rs.close();
    statement.close();
    connection.close();
}

1)說明:

  • 在連接參數(shù)中需要拼接useCursorFetch=true;
  • 創(chuàng)建Statement時需要設(shè)置ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY
  • 設(shè)置fetchSize控制每一次獲取多少條數(shù)據(jù)

2)抓包驗證:

通過wireshark抓包,可以看到每執(zhí)行一次rs.next() 就會向mysql服務(wù)發(fā)送一個請求,同時mysql服務(wù)返回兩條數(shù)據(jù):

3)游標查詢需要注意的點:

由于MySQL方不知道客戶端什么時候?qū)?shù)據(jù)消費完,而自身的對應(yīng)表可能會有DML寫入操作,此時MySQL需要建立一個臨時空間來存放需要拿走的數(shù)據(jù)。

因此對于當(dāng)你啟用useCursorFetch讀取大表的時候會看到MySQL上的幾個現(xiàn)象:

  1. IOPS飆升 (IOPS (Input/Output Per Second):磁盤每秒的讀寫次數(shù))
  2. 磁盤空間飆升
  3. 客戶端JDBC發(fā)起SQL后,長時間等待SQL響應(yīng)數(shù)據(jù),這段時間就是服務(wù)端在準備數(shù)據(jù)
  4. 在數(shù)據(jù)準備完成后,開始傳輸數(shù)據(jù)的階段,網(wǎng)絡(luò)響應(yīng)開始飆升,IOPS由“讀寫”轉(zhuǎn)變?yōu)?ldquo;讀取”。
  5. CPU和內(nèi)存會有一定比例的上升

到此這篇關(guān)于Mysql中JDBC的三種查詢(普通、流式、游標)詳解的文章就介紹到這了,更多相關(guān)JDBC的三種查詢內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • mysql導(dǎo)出查詢結(jié)果到csv的實現(xiàn)方法

    mysql導(dǎo)出查詢結(jié)果到csv的實現(xiàn)方法

    下面小編就為大家?guī)硪黄猰ysql導(dǎo)出查詢結(jié)果到csv的實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-04-04
  • mysql 無法聯(lián)接常見故障及原因分析

    mysql 無法聯(lián)接常見故障及原因分析

    這篇文章主要介紹了mysql 無法聯(lián)接常見故障及原因分析,本文是小編日常收集整理的,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2017-11-11
  • mysql給一張表添加外鍵的4種方法

    mysql給一張表添加外鍵的4種方法

    這篇文章主要給大家介紹了關(guān)于mysql給一張表添加外鍵的4種方法,MySQL是一種常用的關(guān)系型數(shù)據(jù)庫管理系統(tǒng),它支持外鍵約束以保證數(shù)據(jù)庫的數(shù)據(jù)完整性,需要的朋友可以參考下
    2023-08-08
  • 淺析刪除表的幾種方法(delete、drop、truncate)

    淺析刪除表的幾種方法(delete、drop、truncate)

    這篇文章主要介紹了刪除表的幾種方法,需要的朋友可以參考下
    2014-05-05
  • Mysql 8 新特性 window functions 的作用

    Mysql 8 新特性 window functions 的作用

    MySQL是眾多網(wǎng)站技術(shù)棧中的標準配置,是廣受歡迎的開源數(shù)據(jù)庫,已經(jīng)推出了8.0的第一個候選發(fā)行版本。接下來通過本文給大家分享Mysql 8 新特性 window functions 的作用,需要的朋友參考下吧
    2017-11-11
  • mysql批量刪除數(shù)據(jù)方法及注意事項說明

    mysql批量刪除數(shù)據(jù)方法及注意事項說明

    這篇文章主要介紹了mysql批量刪除數(shù)據(jù)方法及注意事項說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • 如何解決MYSQL8.4.1 MySQL84--ERROR 1524(HY000):Plugin ‘msql_native_password‘ is not loaded問題

    如何解決MYSQL8.4.1 MySQL84--ERROR 1524(HY000):Plugin&n

    這篇文章主要介紹了如何解決MYSQL8.4.1 MySQL84--ERROR 1524(HY000):Plugin ‘msql_native_password‘ is not loaded問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • mysql通過binlog日志復(fù)制主從同步的實現(xiàn)

    mysql通過binlog日志復(fù)制主從同步的實現(xiàn)

    本文主要介紹了mysql通過binlog日志復(fù)制主從同步的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-05-05
  • MySql常用查詢命令操作大全

    MySql常用查詢命令操作大全

    本文給大家收集整理了MySql常用查詢命令操作大全,非常不錯,具有參考借鑒價值,需要的朋友參考下吧
    2017-11-11
  • 詳解MySQL與Spring的自動提交(autocommit)

    詳解MySQL與Spring的自動提交(autocommit)

    這篇文章主要介紹了MySQL與Spring的自動提交(autocommit)的的相關(guān)資料,幫助大家更好的理解和使用MySQL與spring,感興趣的朋友可以了解下
    2021-01-01

最新評論