Mybatis集成MySQL使用游標(biāo)查詢處理大批量數(shù)據(jù)方式
背景
基于數(shù)據(jù)的時(shí)間范圍查詢,給符合條件的用戶推送積分即將到期的提醒。
初期用戶量小使用最普通簡(jiǎn)單的分頁(yè)查詢掃描數(shù)據(jù)處理數(shù)據(jù)沒(méi)問(wèn)題。隨著用戶量的上升表數(shù)據(jù)已經(jīng)上千萬(wàn),每天掃描處理的數(shù)量也超百萬(wàn),limit分頁(yè)出現(xiàn)了慢sql,任務(wù)執(zhí)行時(shí)間也達(dá)不到預(yù)期了。
上述方案出現(xiàn)瓶頸后考慮放棄limit方案,使用游標(biāo)的方式進(jìn)行全量數(shù)據(jù)的獲取,這樣一來(lái)SQL執(zhí)行快任務(wù)執(zhí)行也快。
MySQL游標(biāo)查詢
useCursorFetch
使用游標(biāo)查詢時(shí),必須在jdbc url上設(shè)置連接屬性參數(shù)useCursorFetch=true

FetchSize
在設(shè)置了useCursorFetch=true后,需要在SQL中指定fetchSize,即一次獲取的數(shù)據(jù)量。
如果不設(shè)置fetchSize參數(shù),則執(zhí)行時(shí)仍然是全量返回,可能會(huì)出現(xiàn)OOM。
Mybatis集成Cursor查詢
mapper接口方法返回值聲明為Cursor類型,下面是SQL和Mapper的示例。
Cursor<Long> selectExpireCouponMember(@Param("endTime") String endTime);
<select id="selectExpireCouponMember" resultType="java.lang.Long" fetchSize="5000">
select
distinct member_id
from t_dj_coupon
where
end_time > end_time < #{endStartTime} and end_time < #{endTime}
</select>下面是基于上面的SQL做大量數(shù)據(jù)查詢后寫入文件的代碼示例。
String fileName = DateFormatUtils.format(System.currentTimeMillis(), DateUtils.PATTERN_YYYY_MM_DD) + "_status_push_member.txt";
File file = new File(fileName);
file.createNewFile();
fileWriter = new FileWriter(file);
bufferedWriter = new BufferedWriter(fileWriter);
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
DjCouponMapper mapper = sqlSession.getMapper(DjCouponMapper.class);
try (Cursor<Long> cursor = mapper.selectStartCouponMember(startDate, startDateEnd, BrandContextHolder.getBrandMdCode())) {
Iterator<Long> iterator = cursor.iterator();
Set<Long> couponMemberSet = new HashSet<>(pageSize.intValue());
while (iterator.hasNext()) {
couponMemberSet.add(iterator.next());
writeNum++;
if (couponMemberSet.size() >= pageSize) {
bufferedWriter.write(couponMemberSet.toString());
bufferedWriter.newLine();
bufferedWriter.flush();
writeLine++;
couponMemberSet.clear();
}
}
if (CollectionUtils.isNotEmpty(couponMemberSet)) {
bufferedWriter.write(couponMemberSet.toString());
bufferedWriter.newLine();
bufferedWriter.flush();
writeLine++;
}
}
}Mybatis是如何實(shí)現(xiàn)基于Cursor查詢的
com.mysql.cj.jdbc.result.ResultSetImpl實(shí)現(xiàn)類
ResultSetImpl 是mybatis中實(shí)現(xiàn)游標(biāo)查詢結(jié)果解析的類。
這個(gè)實(shí)現(xiàn)類的next方法中調(diào)用了ResultsetRows接口的next方法。

ResultsetRows接口
ResultsetRows接口有ResultsetRowsCursor,ResultsetRowsStatic,ResultsetRowsStreaming三個(gè)實(shí)現(xiàn)類。
本文寫的游標(biāo)查詢的場(chǎng)景,使用的是ResultsetRowsCursor這個(gè)實(shí)現(xiàn)類。

詳細(xì)看下ResultsetRowsCursor這個(gè)實(shí)現(xiàn)類,主要是實(shí)現(xiàn)了Iterator的hasNext和next方法,這也是使用Cursor獲取數(shù)據(jù)需要的兩個(gè)方法。
下面是hasnext方法的邏輯,根據(jù)下次要獲取的游標(biāo)索引和當(dāng)前本地?cái)?shù)據(jù)集計(jì)算返回是否還有后續(xù)數(shù)據(jù)可以獲取。

下面是next方法的邏輯,先執(zhí)行一下hasnext的邏輯判斷,再取值

總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
mysql之delete刪除記錄后數(shù)據(jù)庫(kù)大小不變
這篇文章主要介紹了mysql之delete刪除記錄后數(shù)據(jù)庫(kù)大小不變的相關(guān)資料,需要的朋友可以參考下2016-06-06
解決阿里云ECS服務(wù)器下安裝MySQL無(wú)法遠(yuǎn)程連接的問(wèn)題
這篇文章介紹了解決阿里云ECS服務(wù)器安裝MySQL無(wú)法遠(yuǎn)程連接的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
MySQL刪除表時(shí)I/O錯(cuò)誤的原因分析與解決
這篇文章主要給大家介紹了關(guān)于MySQL刪除表時(shí)I/O錯(cuò)誤的原因分析與解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08
完美解決MySQL通過(guò)localhost無(wú)法連接數(shù)據(jù)庫(kù)的問(wèn)題
下面小編就為大家?guī)?lái)一篇完美解決MySQL通過(guò)localhost無(wú)法連接數(shù)據(jù)庫(kù)的問(wèn)題。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02
安裝MySQL在最后的start service停住了解決方法
今天為一個(gè)客戶配置服務(wù)器的時(shí)候,發(fā)現(xiàn)的問(wèn)題,原來(lái)他自己安裝過(guò)mysql但安全沒(méi)有配置好,路徑選擇的也不好,重新安裝后發(fā)現(xiàn)在start service卡住了,通過(guò)下面的方法解決了,特分享下2013-11-11

