一文帶你搞懂mysql中的三種數(shù)據(jù)讀取方式
在與MySQL數(shù)據(jù)庫(kù)交互時(shí),數(shù)據(jù)的讀取方式有多種選擇,包括流式讀取、游標(biāo)讀取和普通讀取。每種方式都有其獨(dú)特的原理、優(yōu)勢(shì)和劣勢(shì)。本文將對(duì)這三種讀取方式進(jìn)行詳細(xì)介紹,
1. 普通讀取
介紹
普通讀取是指通過JDBC的Statement
或PreparedStatement
執(zhí)行SQL查詢,JDBC驅(qū)動(dòng)會(huì)阻塞的一次性讀取全部查詢的數(shù)據(jù)到 JVM 內(nèi)存中。這種方式適用于小型數(shù)據(jù)集的讀取。
原理
在普通讀取中,當(dāng)執(zhí)行查詢時(shí),JDBC會(huì)將整個(gè)結(jié)果集從數(shù)據(jù)庫(kù)加載到內(nèi)存中。開發(fā)者可以通過ResultSet
對(duì)象逐行訪問數(shù)據(jù)。
示例代碼
Connection connection = null; Statement statement = null; ResultSet resultSet = null; try { String url = "jdbc:mysql://localhost:3307/test?useSSL=false"; connection = DriverManager.getConnection(url, user, password); statement = connection.createStatement(); resultSet = statement.executeQuery("SELECT * FROM table_name"); while (resultSet.next()) { System.out.println(resultSet.getString("column_name")); } } catch (SQLException e) { e.printStackTrace(); } finally { // close if (resultSet != null) resultSet.close(); if (statement != null) statement.close(); if (connection != null) connection.close(); }
優(yōu)勢(shì)
- 簡(jiǎn)單易用:代碼結(jié)構(gòu)簡(jiǎn)單,易于理解和使用。
- 適合小數(shù)據(jù)集:對(duì)于小型數(shù)據(jù)集,性能良好,讀取速度快。
劣勢(shì)
- 內(nèi)存消耗:對(duì)于大型數(shù)據(jù)集,可能導(dǎo)致內(nèi)存消耗過大,甚至引發(fā)
OutOfMemoryError
。 - 不適合實(shí)時(shí)處理:無法實(shí)時(shí)處理數(shù)據(jù),需等待整個(gè)結(jié)果集加載完成。
2. 游標(biāo)讀取
介紹
游標(biāo)讀取是指通過JDBC的Statement
或PreparedStatement
使用游標(biāo)逐行讀取數(shù)據(jù)。游標(biāo)允許在結(jié)果集中移動(dòng),適合處理較大的數(shù)據(jù)集。
原理
游標(biāo)讀取通過在數(shù)據(jù)庫(kù)中維護(hù)一個(gè)指向結(jié)果集的指針,允許逐行訪問數(shù)據(jù)。每次讀取一行數(shù)據(jù),游標(biāo)向前移動(dòng),直到結(jié)果集結(jié)束。
示例代碼
在連接參數(shù)中需要拼接useCursorFetch=true;
創(chuàng)建Statement時(shí)需要設(shè)置ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY
設(shè)置fetchSize控制每一次獲取多少條數(shù)據(jù)
Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { String url ="jdbc:mysql://localhost:3307/test?useSSL=false&useCursorFetch=true"; connection = DriverManager.getConnection(url, user, password); preparedStatement = connection.prepareStatement("SELECT * FROM table_name", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); preparedStatement.setFetchSize(100); //set fetchSize resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { System.out.println(resultSet.getString("column_name")); } } catch (SQLException e) { e.printStackTrace(); } finally { // close reources if (resultSet != null) resultSet.close(); if (preparedStatement != null) preparedStatement.close(); if (connection != null) connection.close(); }
優(yōu)勢(shì)
- 內(nèi)存效率:只在內(nèi)存中保留當(dāng)前行,適合處理大型數(shù)據(jù)集。
- 逐行處理:可以逐行讀取和處理數(shù)據(jù),適合實(shí)時(shí)數(shù)據(jù)處理場(chǎng)景。
劣勢(shì)
- 復(fù)雜性:相較于普通讀取,代碼結(jié)構(gòu)稍復(fù)雜。
- 性能開銷:在某些情況下,逐行讀取可能會(huì)導(dǎo)致性能下降。
游標(biāo)查詢需要注意的點(diǎn):
由于MySQL方不知道客戶端什么時(shí)候?qū)?shù)據(jù)消費(fèi)完,而自身的對(duì)應(yīng)表可能會(huì)有DML寫入操作,此時(shí)MySQL需要建立一個(gè)臨時(shí)空間來存放需要拿走的數(shù)據(jù)。因此對(duì)于當(dāng)你啟用useCursorFetch讀取大表的時(shí)候會(huì)看到MySQL上的幾個(gè)現(xiàn)象:
- IOPS飆升 (IOPS (Input/Output Per Second):磁盤每秒的讀寫次數(shù))
- 磁盤空間飆升
- 客戶端JDBC發(fā)起SQL后,長(zhǎng)時(shí)間等待SQL響應(yīng)數(shù)據(jù),這段時(shí)間就是服務(wù)端在準(zhǔn)備數(shù)據(jù)
- 在數(shù)據(jù)準(zhǔn)備完成后,開始傳輸數(shù)據(jù)的階段,網(wǎng)絡(luò)響應(yīng)開始飆升,IOPS由“讀寫”轉(zhuǎn)變?yōu)?ldquo;讀取”。
- CPU和內(nèi)存會(huì)有一定比例的上升
3. 流式讀取
介紹
流式讀取是指通過JDBC的Statement
或PreparedStatement
以流的方式讀取數(shù)據(jù),適合處理非常大的數(shù)據(jù)集。
原理
流式讀取通過設(shè)置ResultSet
的類型和并發(fā)模式,允許在不將整個(gè)結(jié)果集加載到內(nèi)存的情況下,逐行讀取數(shù)據(jù)。通常結(jié)合setFetchSize()
方法來控制每次從數(shù)據(jù)庫(kù)中獲取的行數(shù)。
示例代碼
Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { String url = "jdbc:mysql://localhost:3307/test?useSSL=false"; connection = DriverManager.getConnection(url, user, password); preparedStatement = connection.prepareStatement("SELECT * FROM table_name", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); preparedStatement.setFetchSize(1000); // 設(shè)置每次讀取的行數(shù) //OR USEING com.mysql.jdbc.StatementImpl //((StatementImpl) statement).enableStreamingResults(); resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { System.out.println(resultSet.getString("column_name")); } } catch (SQLException e) { e.printStackTrace(); } finally { // 關(guān)閉資源 if (resultSet != null) resultSet.close(); if (preparedStatement != null) preparedStatement.close(); if (connection != null) connection.close(); }
優(yōu)勢(shì)
- 極高的內(nèi)存效率:適合處理超大數(shù)據(jù)集,內(nèi)存占用極低。
- 實(shí)時(shí)處理能力:可以實(shí)時(shí)處理數(shù)據(jù),適合流式數(shù)據(jù)分析。
劣勢(shì)
- 復(fù)雜性:實(shí)現(xiàn)相對(duì)復(fù)雜,需要合理設(shè)置
fetch size
。 - 性能問題:在某些情況下,頻繁的數(shù)據(jù)庫(kù)訪問可能導(dǎo)致性能下降。
流式查詢應(yīng)該注意的坑
WARN ] 2024-12-26 09:36:50.365 [] job-file-log-676bc326966a463e08520799 - [srtosr][sr35] - Query 'his_config_info_exp' snapshot row size failed: java.lang.RuntimeException: io.tapdata.flow.engine.V2.exception.node.NodeException: Query table 'his_config_info_exp' count failed: No operations allowed after connection closed.
java.util.concurrent.CompletionException: java.lang.RuntimeException: io.tapdata.flow.engine.V2.exception.node.NodeException: Query table 'his_config_info_exp' count failed: No operations allowed after connection closed.
at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
at java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1643)
at java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:1632)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
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)通過流式查詢獲取一個(gè)ResultSet后,通過next迭代出所有元素之前或者調(diào)用close關(guān)閉它之前,不能使用同一個(gè)數(shù)據(jù)庫(kù)連接去發(fā)起另外一個(gè)查詢,否者拋出異常(第一次調(diào)用的正常,第二次的拋出異常)。
總結(jié)
在選擇MySQL的數(shù)據(jù)讀取方式時(shí),需要根據(jù)具體的應(yīng)用場(chǎng)景和數(shù)據(jù)集大小來決定:
- 普通讀取適合小型數(shù)據(jù)集,簡(jiǎn)單易用,但內(nèi)存消耗較大。
- 游標(biāo)讀取適合中型數(shù)據(jù)集,內(nèi)存效率較高,逐行處理。
- 流式讀取適合超大數(shù)據(jù)集,內(nèi)存占用極低,實(shí)時(shí)處理能力強(qiáng),但實(shí)現(xiàn)復(fù)雜。
根據(jù)實(shí)際需求,選擇合適的讀取方式可以提高應(yīng)用程序的性能和可擴(kuò)展性。
到此這篇關(guān)于一文帶你搞懂mysql中的三種數(shù)據(jù)讀取方式的文章就介紹到這了,更多相關(guān)mysql數(shù)據(jù)讀取內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
FROM_UNIXTIME 格式化MYSQL時(shí)間戳函數(shù)
對(duì)MYSQL沒有進(jìn)行過深入的研究,基礎(chǔ)知識(shí)匱乏,一遇到問題只能手冊(cè),看來要把MYSQL的學(xué)習(xí)安排進(jìn)時(shí)間表了。2011-04-04MySql批量插入優(yōu)化Sql執(zhí)行效率實(shí)例詳解
這篇文章主要介紹了MySql批量插入優(yōu)化Sql執(zhí)行效率實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-04-04CentOS7下mysql 8.0.16 安裝配置方法圖文教程
這篇文章主要為大家詳細(xì)介紹了CentOS7下mysql 8.0.16 安裝配置方法圖文教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05千萬級(jí)記錄的Discuz論壇導(dǎo)致MySQL CPU 100%的優(yōu)化筆記
談到自己在解決一個(gè)擁有 60 萬條記錄的 MySQL 數(shù)據(jù)庫(kù)訪問時(shí),導(dǎo)致 MySQL CPU 占用 100% 的經(jīng)過。在解決問題完成優(yōu)化(optimize)之后,我發(fā)現(xiàn) Discuz 論壇也存在這個(gè)問題,當(dāng)時(shí)稍微提了一下2010-12-12Mysql中文漢字轉(zhuǎn)拼音的實(shí)現(xiàn)(每個(gè)漢字轉(zhuǎn)換全拼)
這篇文章主要介紹了Mysql中文漢字轉(zhuǎn)拼音的實(shí)現(xiàn),并且每個(gè)漢字會(huì)轉(zhuǎn)換全拼,使用Mysql自定義函數(shù)實(shí)現(xiàn),需要的朋友可以參考下2014-06-06