MybatisPlus處理大表查詢的實(shí)現(xiàn)步驟
大數(shù)據(jù)量操作的場(chǎng)景大致如下:
- 數(shù)據(jù)遷移
- 數(shù)據(jù)導(dǎo)出
- 批量處理數(shù)據(jù)
在實(shí)際工作中當(dāng)指定查詢數(shù)據(jù)過(guò)大時(shí),我們一般使用分頁(yè)查詢的方式一頁(yè)一頁(yè)的將數(shù)據(jù)放到內(nèi)存處理。但有些情況不需要分頁(yè)的方式查詢數(shù)據(jù)或分很大一頁(yè)查詢數(shù)據(jù)時(shí),如果一下子將數(shù)據(jù)全部加載出來(lái)到內(nèi)存中,很可能會(huì)發(fā)生OOM(內(nèi)存溢出);而且查詢會(huì)很慢,因?yàn)榭蚣芎馁M(fèi)大量的時(shí)間和內(nèi)存去把數(shù)據(jù)庫(kù)查詢的結(jié)果封裝成我們想要的對(duì)象(實(shí)體類)。
舉例:在業(yè)務(wù)系統(tǒng)需要從 MySQL
數(shù)據(jù)庫(kù)里讀取 100w 數(shù)據(jù)行進(jìn)行處理,應(yīng)該怎么做?
做法通常如下:
- 常規(guī)查詢: 一次性讀取 100w 數(shù)據(jù)到
JVM
內(nèi)存中,或者分頁(yè)讀取 - 流式查詢: 建立長(zhǎng)連接,利用服務(wù)端游標(biāo),每次讀取一條加載到
JVM
內(nèi)存(多次獲取,一次一行) - 游標(biāo)查詢: 和流式一樣,通過(guò)
fetchSize
參數(shù),控制一次讀取多少條數(shù)據(jù)(多次獲取,一次多行)
常規(guī)查詢
默認(rèn)情況下,完整的檢索結(jié)果集會(huì)將其存儲(chǔ)在內(nèi)存中。在大多數(shù)情況下,這是最有效的操作方式,并且由于 MySQL
網(wǎng)絡(luò)協(xié)議的設(shè)計(jì),因此更易于實(shí)現(xiàn)。
舉例:
假設(shè)單表 100w 數(shù)據(jù)量,一般會(huì)采用分頁(yè)的方式查詢:
@Mapper public interface BigDataSearchMapper extends BaseMapper<BigDataSearchEntity> { @Select("SELECT bds.* FROM big_data_search bds ${ew.customSqlSegment} ") Page<BigDataSearchEntity> pageList(@Param("page") Page<BigDataSearchEntity> page, @Param(Constants.WRAPPER) QueryWrapper<BigDataSearchEntity> queryWrapper); }
該方式比較簡(jiǎn)單,如果在不考慮 LIMIT 深分頁(yè)優(yōu)化情況下,估計(jì)你的數(shù)據(jù)庫(kù)服務(wù)器就廢了。
流式查詢
流式查詢指的是查詢成功后不是返回一個(gè)集合而是返回一個(gè)迭代器,應(yīng)用每次從迭代器取一條查詢結(jié)果。流式查詢的好處是能夠降低內(nèi)存使用。
如果沒(méi)有流式查詢,我們想要從數(shù)據(jù)庫(kù)取 100w 條記錄而又沒(méi)有足夠的內(nèi)存時(shí),就不得不分頁(yè)查詢,而分頁(yè)查詢效率取決于表設(shè)計(jì),如果設(shè)計(jì)的不好,就無(wú)法執(zhí)行高效的分頁(yè)查詢。因此流式查詢是一個(gè)數(shù)據(jù)庫(kù)訪問(wèn)框架必須具備的功能。
MyBatis
中使用流式查詢避免數(shù)據(jù)量過(guò)大導(dǎo)致 OOM ,但在流式查詢的過(guò)程當(dāng)中,數(shù)據(jù)庫(kù)連接是保持打開狀態(tài)的,因此要注意的是:
- 執(zhí)行一個(gè)流式查詢后,數(shù)據(jù)庫(kù)訪問(wèn)框架就不負(fù)責(zé)關(guān)閉數(shù)據(jù)庫(kù)連接了,需要應(yīng)用在取完數(shù)據(jù)后自己關(guān)閉。
- 必須先讀?。ɑ蜿P(guān)閉)結(jié)果集中的所有行,然后才能對(duì)連接發(fā)出任何其他查詢,否則將引發(fā)異常。
MyBatis 流式查詢接口
MyBatis
提供了一個(gè)叫 org.apache.ibatis.cursor.Cursor
的接口類用于流式查詢,這個(gè)接口繼承了 java.io.Closeable
和 java.lang.Iterable
接口,由此可知:
- Cursor 是可關(guān)閉的;
- Cursor 是可遍歷的。
除此之外,Cursor 還提供了三個(gè)方法:
- isOpen(): 用于在取數(shù)據(jù)之前判斷 Cursor 對(duì)象是否是打開狀態(tài)。只有當(dāng)打開時(shí) Cursor 才能取數(shù)據(jù);
- isConsumed(): 用于判斷查詢結(jié)果是否全部取完。
- getCurrentIndex(): 返回已經(jīng)獲取了多少條數(shù)據(jù)
使用流式查詢,則要保持對(duì)產(chǎn)生結(jié)果集的語(yǔ)句所引用的表的并發(fā)訪問(wèn),因?yàn)槠洳樵儠?huì)獨(dú)占連接,所以必須盡快處理。
為什么要用流式查詢?
如果有一個(gè)很大的查詢結(jié)果需要遍歷處理,又不想一次性將結(jié)果集裝入客戶端內(nèi)存,就可以考慮使用流式查詢;
分庫(kù)分表場(chǎng)景下,單個(gè)表的查詢結(jié)果集雖然不大,但如果某個(gè)查詢跨了多個(gè)庫(kù)多個(gè)表,又要做結(jié)果集的合并、排序等動(dòng)作,依然有可能撐爆內(nèi)存;詳細(xì)研究了sharding-sphere
的代碼不難發(fā)現(xiàn),除了group by
與order by
字段不一樣之外,其他的場(chǎng)景都非常適合使用流式查詢,可以最大限度的降低對(duì)客戶端內(nèi)存的消耗。
游標(biāo)查詢
對(duì)大量數(shù)據(jù)進(jìn)行處理時(shí),為防止內(nèi)存泄漏情況發(fā)生,也可以采用游標(biāo)方式進(jìn)行數(shù)據(jù)查詢處理,這種處理方式比常規(guī)查詢要快很多。
當(dāng)查詢百萬(wàn)級(jí)的數(shù)據(jù)的時(shí)候,還可以使用游標(biāo)方式進(jìn)行數(shù)據(jù)查詢處理,不僅可以節(jié)省內(nèi)存的消耗,而且還不需要一次性取出所有數(shù)據(jù),可以進(jìn)行逐條處理或逐條取出部分批量處理。一次查詢指定 fetchSize
的數(shù)據(jù),直到把數(shù)據(jù)全部處理完。
Mybatis
的處理加了兩個(gè)注解:@Options
和 @ResultType
@Mapper public interface BigDataSearchMapper extends BaseMapper<BigDataSearchEntity> { // 方式一 多次獲取,一次多行 @Select("SELECT bds.* FROM big_data_search bds ${ew.customSqlSegment} ") @Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = 1000000) Page<BigDataSearchEntity> pageList(@Param("page") Page<BigDataSearchEntity> page, @Param(Constants.WRAPPER) QueryWrapper<BigDataSearchEntity> queryWrapper); // 方式二 一次獲取,一次一行 @Select("SELECT bds.* FROM big_data_search bds ${ew.customSqlSegment} ") @Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = 100000) @ResultType(BigDataSearchEntity.class) void listData(@Param(Constants.WRAPPER) QueryWrapper<BigDataSearchEntity> queryWrapper, ResultHandler<BigDataSearchEntity> handler); }
@Options
ResultSet.FORWORD_ONLY
:結(jié)果集的游標(biāo)只能向下滾動(dòng)ResultSet.SCROLL_INSENSITIVE
:結(jié)果集的游標(biāo)可以上下移動(dòng),當(dāng)數(shù)據(jù)庫(kù)變化時(shí),當(dāng)前結(jié)果集不變ResultSet.SCROLL_SENSITIVE
:返回可滾動(dòng)的結(jié)果集,當(dāng)數(shù)據(jù)庫(kù)變化時(shí),當(dāng)前結(jié)果集同步改變fetchSize
:每次獲取量
@ResultType
@ResultType(BigDataSearchEntity.class)
:轉(zhuǎn)換成返回實(shí)體類型
注意:返回類型必須為 void ,因?yàn)椴樵兊慕Y(jié)果在
ResultHandler
里處理數(shù)據(jù),所以這個(gè) hander 也是必須的,可以使用 lambda 實(shí)現(xiàn)一個(gè)依次處理邏輯。
注意:
雖然上面的代碼中都有 @Options
但實(shí)際操作卻有不同:
- 方式一是多次查詢,一次返回多條;
- 方式二是一次查詢,一次返回一條;
原因:
Oracle 是從服務(wù)器一次取出 fetch size
條記錄放在客戶端,客戶端處理完成一個(gè)批次后再向服務(wù)器取下一個(gè)批次,直到所有數(shù)據(jù)處理完成。
MySQL
是在執(zhí)行 ResultSet.next()
方法時(shí),會(huì)通過(guò)數(shù)據(jù)庫(kù)連接一條一條的返回。flush buffer
的過(guò)程是阻塞式的,如果網(wǎng)絡(luò)中發(fā)生了擁塞,send buffer
被填滿,會(huì)導(dǎo)致 buffer
一直 flush
不出去,那 MySQL
的處理線程會(huì)阻塞,從而避免數(shù)據(jù)把客戶端內(nèi)存撐爆。
非流式查詢和流式查詢區(qū)別:
- 非流式查詢:內(nèi)存會(huì)隨著查詢記錄的增長(zhǎng)而近乎直線增長(zhǎng)。
- 流式查詢:內(nèi)存會(huì)保持穩(wěn)定,不會(huì)隨著記錄的增長(zhǎng)而增長(zhǎng)。其內(nèi)存大小取決于批處理大小
BATCH_SIZE
的設(shè)置,該尺寸越大,內(nèi)存會(huì)越大。所以BATCH_SIZE應(yīng)該根據(jù)業(yè)務(wù)情況設(shè)置合適的大小。
另外要切記每次處理完一批結(jié)果要記得釋放存儲(chǔ)每批數(shù)據(jù)的臨時(shí)容器。
到此這篇關(guān)于MybatisPlus處理大表查詢的實(shí)現(xiàn)步驟的文章就介紹到這了,更多相關(guān)MybatisPlus 大表查詢內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- MybatisPlus多表查詢及分頁(yè)查詢完整代碼
- SpringBoot整合Mybatis-Plus實(shí)現(xiàn)關(guān)聯(lián)查詢
- mybatis-plus分頁(yè)查詢的實(shí)現(xiàn)實(shí)例
- 深入解析MybatisPlus多表連接查詢
- mybatis-plus @select動(dòng)態(tài)查詢方式
- MybatisPlus條件查詢的具體使用
- mybatis-plus多表查詢操作方法
- SpringBoot使用mybatis-plus分頁(yè)查詢無(wú)效的問(wèn)題解決
- SpringBoot整合mybatis-plus實(shí)現(xiàn)分頁(yè)查詢功能
- Mybatisplus集成springboot完成分頁(yè)查詢功能(示例代碼)
相關(guān)文章
解決@MapperScan和@Mapper共存之坑X(jué)xxMapper?that?could?not?be?fo
這篇文章主要介紹了解決@MapperScan和@Mapper共存之坑X(jué)xxMapper?that?could?not?be?found問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06Spring Boot使用Servlet及Filter過(guò)程詳解
這篇文章主要介紹了Spring Boot使用Servlet及Filter過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07詳解Java Callable接口實(shí)現(xiàn)多線程的方式
這篇文章主要介紹了詳解Java Callable接口實(shí)現(xiàn)多線程的方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04java web個(gè)人通訊錄系統(tǒng)設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了java web個(gè)人通訊錄系統(tǒng)設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01Java使用easyExcel實(shí)現(xiàn)Excel文件解析
這篇文章主要為大家詳細(xì)介紹了Java如何使用easyExcel實(shí)現(xiàn)Excel文件解析,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-02-02