自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

被問傻了:從MySQL讀取100w數(shù)據(jù)進(jìn)行處理,怎么做?

數(shù)據(jù)庫 MySQL
默認(rèn)情況下,完整的檢索結(jié)果集會(huì)將其存儲(chǔ)在內(nèi)存中。在大多數(shù)情況下,這是最有效的操作方式,并且由于 MySQL 網(wǎng)絡(luò)協(xié)議的設(shè)計(jì),因此更易于實(shí)現(xiàn)。

大數(shù)據(jù)量操作的場景大致如下:

  • 數(shù)據(jù)遷移
  • 數(shù)據(jù)導(dǎo)出
  • 批量處理數(shù)據(jù)

在實(shí)際工作中當(dāng)指定查詢數(shù)據(jù)過大時(shí),我們一般使用分頁查詢的方式一頁一頁的將數(shù)據(jù)放到內(nèi)存處理。但有些情況不需要分頁的方式查詢數(shù)據(jù)或分很大一頁查詢數(shù)據(jù)時(shí),如果一下子將數(shù)據(jù)全部加載出來到內(nèi)存中,很可能會(huì)發(fā)生OOM(內(nèi)存溢出);而且查詢會(huì)很慢,因?yàn)榭蚣芎馁M(fèi)大量的時(shí)間和內(nèi)存,去把數(shù)據(jù)庫查詢的結(jié)果封裝成我們想要的對象(實(shí)體類)。

舉例:在業(yè)務(wù)系統(tǒng)需要從 MySQL 數(shù)據(jù)庫里讀取 100w 數(shù)據(jù)行進(jìn)行處理,應(yīng)該怎么做?

做法通常如下:

  • 常規(guī)查詢: 一次性讀取 100w 數(shù)據(jù)到 JVM 內(nèi)存中,或者分頁讀取
  • 流式查詢:建立長連接,利用服務(wù)端游標(biāo),每次讀取一條加載到 JVM 內(nèi)存(多次獲取,一次一行)
  • 游標(biāo)查詢: 和流式一樣,通過 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ì)采用分頁的方式查詢:

@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);


}

注:該示例使用的 MybatisPlus。

該方式比較簡單,如果在不考慮 LIMIT 深分頁優(yōu)化情況下,估計(jì)你的數(shù)據(jù)庫服務(wù)器就噶皮了,或者你能等上幾十分鐘或幾小時(shí),甚至幾天時(shí)間檢索數(shù)據(jù)。

二、流式查詢

流式查詢指的是查詢成功后不是返回一個(gè)集合而是返回一個(gè)迭代器,應(yīng)用每次從迭代器取一條查詢結(jié)果。流式查詢的好處是能夠降低內(nèi)存使用。

如果沒有流式查詢,我們想要從數(shù)據(jù)庫取 100w 條記錄而又沒有足夠的內(nèi)存時(shí),就不得不分頁查詢,而分頁查詢效率取決于表設(shè)計(jì),如果設(shè)計(jì)的不好,就無法執(zhí)行高效的分頁查詢。因此流式查詢是一個(gè)數(shù)據(jù)庫訪問框架必須具備的功能。

MyBatis 中使用流式查詢避免數(shù)據(jù)量過大導(dǎo)致 OOM ,但在流式查詢的過程當(dāng)中,數(shù)據(jù)庫連接是保持打開狀態(tài)的,因此要注意的是:

  • 執(zhí)行一個(gè)流式查詢后,數(shù)據(jù)庫訪問框架就不負(fù)責(zé)關(guān)閉數(shù)據(jù)庫連接了,需要應(yīng)用在取完數(shù)據(jù)后自己關(guān)閉
  • 必須先讀?。ɑ蜿P(guān)閉)結(jié)果集中的所有行,然后才能對連接發(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 對象是否是打開狀態(tài)。只有當(dāng)打開時(shí) Cursor 才能取數(shù)據(jù)
  • isConsumed(): 用于判斷查詢結(jié)果是否全部取完
  • getCurrentIndex(): 返回已經(jīng)獲取了多少條數(shù)據(jù)

使用流式查詢,則要保持對產(chǎn)生結(jié)果集的語句所引用的表的并發(fā)訪問,因?yàn)槠洳樵儠?huì)獨(dú)占連接,所以必須盡快處理。

為什么要用流式查詢?

如果有一個(gè)很大的查詢結(jié)果需要遍歷處理,又不想一次性將結(jié)果集裝入客戶端內(nèi)存,就可以考慮使用流式查詢。

分庫分表場景下,單個(gè)表的查詢結(jié)果集雖然不大,但如果某個(gè)查詢跨了多個(gè)庫多個(gè)表,又要做結(jié)果集的合并、排序等動(dòng)作,依然有可能撐爆內(nèi)存;詳細(xì)研究了sharding-sphere的代碼不難發(fā)現(xiàn),除了group by與order by字段不一樣之外,其他的場景都非常適合使用流式查詢,可以最大限度的降低對客戶端內(nèi)存的消耗。

三、游標(biāo)查詢

對大量數(shù)據(jù)進(jìn)行處理時(shí),為防止內(nèi)存泄漏情況發(fā)生,也可以采用游標(biāo)方式進(jìn)行數(shù)據(jù)查詢處理。這種處理方式比常規(guī)查詢要快很多。

當(dāng)查詢百萬級的數(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ù)庫變化時(shí),當(dāng)前結(jié)果集不變
  • ResultSet.SCROLL_SENSITIVE:返回可滾動(dòng)的結(jié)果集,當(dāng)數(shù)據(jù)庫變化時(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ì)通過數(shù)據(jù)庫連接一條一條的返回。flush buffer 的過程是阻塞式的,如果網(wǎng)絡(luò)中發(fā)生了擁塞,send buffer 被填滿,會(huì)導(dǎo)致 buffer 一直 flush 不出去,那 MySQL 的處理線程會(huì)阻塞,從而避免數(shù)據(jù)把客戶端內(nèi)存撐爆。

非流式查詢和流式查詢區(qū)別:

  • 非流式查詢:內(nèi)存會(huì)隨著查詢記錄的增長而近乎直線增長
  • 流式查詢:內(nèi)存會(huì)保持穩(wěn)定,不會(huì)隨著記錄的增長而增長。其內(nèi)存大小取決于批處理大小BATCH_SIZE的設(shè)置,該尺寸越大,內(nèi)存會(huì)越大。所以BATCH_SIZE應(yīng)該根據(jù)業(yè)務(wù)情況設(shè)置合適的大小

另外要切記每次處理完一批結(jié)果要記得釋放存儲(chǔ)每批數(shù)據(jù)的臨時(shí)容器,即上文中的gxids.clear();

責(zé)任編輯:武曉燕 來源: JAVA日知錄
相關(guān)推薦

2021-06-23 06:48:42

秒殺Java電商

2019-08-23 09:03:04

盤口數(shù)據(jù)數(shù)據(jù)庫緩存

2023-09-27 22:44:18

數(shù)據(jù)遷移數(shù)據(jù)庫

2021-07-09 05:52:36

架構(gòu)開發(fā)緩存

2011-04-06 14:50:05

SQL查詢效率

2019-09-16 09:34:39

2024-09-05 21:24:02

數(shù)據(jù)庫查詢MySQLlimit

2022-11-16 17:10:25

MySQL數(shù)據(jù)事務(wù)

2016-06-28 10:13:04

華為開發(fā)者大賽

2020-07-28 08:36:54

數(shù)據(jù)安全數(shù)據(jù)泄露數(shù)據(jù)

2011-03-11 09:53:46

FacebookMySQL

2017-11-08 12:25:37

小程序運(yùn)營公眾號(hào)

2022-03-10 11:25:51

InnoDB優(yōu)化

2016-01-05 16:17:59

云夢數(shù)據(jù)倉

2015-09-20 18:31:29

阿里云心電數(shù)據(jù)云上安心

2021-10-12 10:22:33

數(shù)據(jù)庫架構(gòu)技術(shù)

2021-11-10 05:00:58

數(shù)據(jù)分析運(yùn)營

2018-02-07 09:00:09

2025-01-02 09:17:37

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)