? ? ? ? 最近在做一個計算相關的功能,大體就是有很多條SQL,每條SQL都涉及復雜地運算,最后要將所有計算結果進行合并分析。經(jīng)初步測試,每個SQL起碼會查出幾十萬條記錄,我們現(xiàn)在有毛毛多的這種SQL。
? ? ? ? 最大的問題不在于速度,畢竟涉及運算的功能,想要從速度入手就得靠中間件和算法了。內存占用才是我們最需要注意的,一旦數(shù)據(jù)量很大且一次性沖入Java堆內存,程序會直接OOM然后離開人世。比如使用非分頁的普通查詢,這張表1000w條數(shù)據(jù)你有多少要多少,除非你設置-xms 128g,否則程序是一定會死的。
? ? ? ? 當然平時我們一般都會指定分頁參數(shù),但遇到大數(shù)據(jù)量查詢時,為了內存的身體健康,還是需要一些特殊的方式——流式查詢與游標查詢。
1 流式查詢
? ? ? ?采用傳統(tǒng)的Stream流式思想,將直接提供數(shù)據(jù)替換成提供獲取數(shù)據(jù)的管道,客戶端讀取數(shù)據(jù)時直接從管道中遍歷獲取;整個讀取的過程需要客戶端保持和服務端的連接,也很好理解,它實際是一個管道,管道得通著才能取數(shù)據(jù)。
? ? ? ? 流式查詢有兩種使用方式,一種是用Cursor<T>作為返回值,對數(shù)據(jù)進行遍歷操作;一種是不設置返回值,在入?yún)⒅袀魅胍粋€ResultHandler<T>作為回調處理數(shù)據(jù)。本文將基于Mybatis具體介紹使用方法。
? ? ? ? 這兩種返回值的使用方式是相似的,唯一區(qū)別就是返回值不同。Mybatis查詢有兩種方式,一種是基于注解加在Mapper接口上方,一種是寫在xml文件中,主要需要設置以下幾個屬性:
ResultSetType | 結果集讀取方式 |
FetchSize | MySQL服務端單次發(fā)送至客戶端的數(shù)據(jù)條數(shù) |
ResultType | 這個眼熟吧,設置返回實體類映射 |
? ? ? ? ResultType沒什么好說的,一般Mybatis查詢都會用到,我們著重介紹一下ResultSetType和FetchSize。ResultSetType有4種可選項,我們點進去看看,DEFAULT不談,主要是下面三種。?
DEFAULT(-1),
FORWARD_ONLY(1003),
SCROLL_INSENSITIVE(1004),
SCROLL_SENSITIVE(1005);
- FORWARD_ONLY,顧名思義只能向前,即數(shù)據(jù)只能向前讀取,是不是就類似一個流水的管道,讀一條就相當于水流過去一些。也是我們需要選用的。
- SCROLL_INSENSITIVE,不敏感滾動,和下面那個差不多,都是可以向后讀或向前讀;這意味著已讀取過的數(shù)據(jù)不能丟掉,要繼續(xù)保存在內存中,因為有可能會回去再次讀取他們。
- SCROLL_SENSITIVE,敏感滾動,和上面那個差不多。
? ? ? ? 這么一比較就看得出來,當選的一定是FORWARD_ONLY,我們亟需解決的就是大數(shù)據(jù)量對內存的影響,再用后面兩個還是會放在內存中。
? ? ? ? 再看FetchSize,這個概念在許多服務中都有提及,例如RabbitMQ中是消費者取過來預處理的消息數(shù)量,但在MySQL中完全不是一個概念。MySQL的數(shù)據(jù)傳輸是基于C/S的阻塞機制,即Client設置FetchSize = 1000,而Server查出來10000條數(shù)據(jù),按照常理應該是Server智能地使用分頁策略1000條1000條取;實際不是,Server查出來多少就是多少,他會放在自己特定的內存空間內,只是會根據(jù)FetchSize的大小一點一點傳送給Client——利用C/S的通訊阻塞,發(fā)1000條、堵一下、發(fā)1000條、堵一下……。
? ? ? ? 話又說回來,怎么配置這個FetchSize呢?JDBC官方給出的答案是設置為“Integer.MIN_VALUE”,具體原因不清楚,但我猜是為了和游標查詢區(qū)分開,因為一會你會發(fā)現(xiàn)流式查詢和游標查詢唯一的區(qū)別就是FetchSize的大小。
? ? ? ? 設置好這3個參數(shù)以后,就可以用Cursor或者ResultHandler來處理返回值了。有如下幾種寫法。
(1)注解式?
@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = Integer.MIN_VALUE)
@ResultType(ResultVo.class)
@Select("SELECT *, 0 orderType FROM `table`\n" +
" WHERE username = #{userName}")
Cursor<ResultVo> listOrders(@Param("userName") String userName);
@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = Integer.MIN_VALUE)
@ResultType(ResultVo.class)
@Select("SELECT *, 0 orderType FROM `table`\n" +
" WHERE username = #{userName}")
void listOrders2(@Param("userName") String userName, ResultHandler<ResultVo> handler);
? ? ? ? 使用Mybatis的注解,在@Options中指定查詢配置參數(shù),在@ResultType中指定返回值類型?,在@Select中指定查詢語句。最后用Cursor接收返回值,Cursor是可遍歷的,所以直接Foreach遍歷即可;或者用ResultHandler處理數(shù)據(jù)回調,在調用方式時傳入new ResultHandler并寫明處理邏輯。
(2)xml方式
<select id="listOrders" resultType="com.vo.ResultVo" resultSetType="FORWARD_ONLY" fetchSize="-2147483648">
SELECT *, 1 stuffCount, 1 orderType FROM `table`
WHERE username = #{userName}
</select>
? ? ? ? 本質是相同的,只不過是將注解中的內容放進了標簽中,返回值和數(shù)據(jù)處理方式也一致。
? ? ? ? 需要注意的是,不可以注解 + xml混合使用,比如注解指定fetchSize,xml只寫查詢語句,這種只有xml語句會生效?。。∫蝗米⒔?,要不全用xml?。。?
2 游標查詢
? ? ? ? 游標查詢主要依靠FetchSize屬性,指定Server每次傳輸給Client數(shù)據(jù)的條數(shù)。這種方式由于我沒有實驗就不賣弄了,使用方式和流式查詢基本一致,只是FetchSize不能是Integer.MIN_VALUE,而是一個真實的數(shù)字,不能太大也不能太小,太大了內存受不了,太小了客戶受不了。
? ? ? ? 不過JDBC查詢默認是不支持FetchSize屬性的,需要在JDBC連接URL后面加上“useCursorFetch=true”。這就不是很理想了,你說你寫個代碼還要改數(shù)據(jù)庫連接的屬性,萬一后面出任何查詢上的問題,都有可能追溯到你頭上,所以我沒敢用游標查詢。
3 注意點
? ? ? ? 流式查詢由于需要保持客戶端與服務端的連接,而一般查詢提交完連接就會關閉;因此我們需要保持事務開啟,否則會報“A Cursor is already closed.”,即Cursor已經(jīng)關閉,沒法再讀取了。最簡單的方法就是在方法上加@Transactional,在查詢完畢以前事務會一直持有這個數(shù)據(jù)庫連接,但我們在使用完畢后也要自行關閉連接,顯式調用Cursor.close(),或者用try with resource語句。
? ? ? ??還要知道如何判斷自己是否使用了流式查詢或游標查詢,下面是幾個數(shù)據(jù)集的對應關系:
查詢方式 | 結果集類型 | 行數(shù)據(jù)類型 |
普通分頁 | ResultsetRowsStatic | RowDataStatic |
流式查詢 | ResultsetRowsStreaming | RowDataDynamic |
游標查詢 | ResultsetRowsCursor | RowDataCursor |
? ? ? ? 我使用到的流式查詢,debug到DefaultResultSetHandler.class中,顯示rsw中確實存儲了ResultSetRowsStreaming。
4 優(yōu)劣分析
? ? ? ? 這3種查詢方式,常規(guī)非大數(shù)據(jù)模式下普通查詢最快,其次是流式查詢,最次是游標查詢。文章來源:http://www.zghlxwxcb.cn/news/detail-615568.html
????????主要是由于游標查詢需要和數(shù)據(jù)庫進行多次網(wǎng)絡交互,Client處理完這部分后再拉取下一部分數(shù)據(jù),因此會比較慢。但是流式查詢又會長時間占用同一個數(shù)據(jù)庫連接,因此要取舍一下是能接受連接一直持有但是可能會堵住導致響應慢,還是可能占用較多連接數(shù)但單次響應快。文章來源地址http://www.zghlxwxcb.cn/news/detail-615568.html
到了這里,關于大數(shù)據(jù)量查詢:流式查詢與游標查詢的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!