<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
現在業務系統需要從 MySQL 資料庫裡讀取 500w 資料行進行處理
預設情況下,完整的檢索結果集會將其儲存在記憶體中。在大多數情況下,這是最有效的操作方式,更易於實現。
假設單表 500w 資料量,沒有人會一次性載入到記憶體中,一般會採用分頁的方式。
在這裡,測試demo中只是為了監控JVM,所以沒有采用分頁,一次性將資料載入記憶體中
@Test public void generalQuery() throws Exception { // 1核2G:查詢一百條記錄:47ms // 1核2G:查詢一千條記錄:2050 ms // 1核2G:查詢一萬條記錄:26589 ms // 1核2G:查詢五萬條記錄:135966 ms String sql = "select * from wh_b_inventory limit 10000"; ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery(sql); int count = 0; while (rs.next()) { count++; } System.out.println(count); }
JVM監控
我們將對記憶體調小-Xms70m -Xmx70m
整個查詢過程中,堆記憶體佔用逐步增長,並且最終導致OOM:
java.lang.OutOfMemoryError: GC overhead limit exceeded
1、頻繁觸發GC
2、存在OOM隱患
流式查詢有一點需要注意:必須先讀取(或關閉)結果集中的所有行,然後才能對連線發出任何其他查詢,否則將引發異常,其 查詢會獨佔連線。
從測試結果來看,流式查詢並沒有提升查詢的速度
@Test public void streamQuery() throws Exception { // 1核2G:查詢一百條記錄:138ms // 1核2G:查詢一千條記錄:2304 ms // 1核2G:查詢一萬條記錄:26536 ms // 1核2G:查詢五萬條記錄:135931 ms String sql = "select * from wh_b_inventory limit 50000"; statement = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); statement.setFetchSize(Integer.MIN_VALUE); ResultSet rs = statement.executeQuery(sql); int count = 0; while (rs.next()) { count++; } System.out.println(count); }
JVM監控
我們將堆記憶體調小-Xms70m -Xmx70m
我們發現即使堆記憶體只有70m,卻依然沒有發生OOM
注意:
1、需要在資料庫連線資訊裡拼接引數 useCursorFetch=true
2、其次設定 Statement 每次讀取資料數量,比如一次讀取 1000
從測試結果來看,遊標查詢在一定程度縮短了查詢速度
@Test public void cursorQuery() throws Exception { Class.forName("com.mysql.jdbc.Driver"); // 注意這裡需要拼接引數,否則就是普通查詢 conn = DriverManager.getConnection("jdbc:mysql://101.34.50.82:3306/mysql-demo?useCursorFetch=true", "root", "123456"); start = System.currentTimeMillis(); // 1核2G:查詢一百條記錄:52 ms // 1核2G:查詢一千條記錄:1095 ms // 1核2G:查詢一萬條記錄:17432 ms // 1核2G:查詢五萬條記錄:90244 ms String sql = "select * from wh_b_inventory limit 50000"; ((JDBC4Connection) conn).setUseCursorFetch(true); statement = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); statement.setFetchSize(1000); ResultSet rs = statement.executeQuery(sql); int count = 0; while (rs.next()) { count++; } System.out.println(count); }
JVM監控
我們將堆記憶體調小-Xms70m -Xmx70m
我們發現在單執行緒情況下,遊標查詢和流式查詢一樣,都能很好的規避OOM,並且遊標查詢能夠優化查詢速度。
ResultSet.next() 的邏輯是實現類 ResultSetImpl 每次都從 RowData 獲取下一行的資料。RowData 是一個介面,實現關係圖如下
預設情況下 ResultSet 會使用 RowDataStatic 範例,在生成 RowDataStatic 物件時就會把 ResultSet 中所有記錄讀到記憶體裡,之後通過 next() 再一條條從記憶體中讀
當採用流式處理時,ResultSet 使用的是 RowDataDynamic 物件,而這個物件 next() 每次呼叫都會發起 IO 讀取單行資料
RowDataCursor 的呼叫為批次處理,然後進行內部快取,流程如下:
總結來說就是:
預設的 RowDataStatic 讀取全部資料到使用者端記憶體中,也就是我們的 JVM;
RowDataDynamic 每次 IO 呼叫讀取一條資料;
RowDataCursor 一次讀取 fetchSize 行,消費完成再發起請求呼叫。
在 JDBC 與 MySQL 伺服器端的互動是通過 Socket 完成的,對應到網路程式設計,可以把 MySQL 當作一個 SocketServer,因此一個完整的請求鏈路應該是:
JDBC 使用者端 -> 使用者端 Socket -> MySQL -> 檢索資料返回 -> MySQL 核心 Socket Buffer -> 網路 -> 使用者端 Socket Buffer -> JDBC 使用者端
普通查詢會將當次查詢到的所有資料載入到JVM,然後再進行處理。
如果查詢資料量過大,會不斷經歷 GC,然後就是記憶體溢位
伺服器端準備好從第一條資料開始返回時,向緩衝區懟入資料,這些資料通過TCP鏈路,懟入使用者端機器的核心緩衝區,JDBC會的inputStream.read()方法會被喚醒去讀取資料,唯一的區別是開啟了stream讀取的時候,每次只是從核心中讀取一個package大小的資料,只是返回一行資料,如果1個package無法組裝1行資料,會再讀1個package。
當開啟遊標的時候,伺服器端返回資料的時候,就會按照fetchSize的大小返回資料了,而使用者端接收資料的時候每次都會把換緩衝區資料全部讀取乾淨,假如資料有1億資料,將FetchSize設定成1000的話,會進行10萬次來回通訊;
由於MySQL方不知道使用者端什麼時候將資料消費完,而自身的對應表可能會有DML寫入操作,此時MySQL需要建立一個臨時空間來存放需要拿走的資料。
因此對於當你啟用useCursorFetch讀取大表的時候會看到MySQL上的幾個現象:
並行呼叫:Jmete 1 秒 10 個執行緒並行呼叫
流式查詢記憶體效能報告如下
並行呼叫對於記憶體佔用情況也很 OK,不存在疊加式增加
遊標查詢記憶體效能報告如下
1、遊標查詢和流式查詢在單執行緒下都能夠規避OOM的情況;
2、在查詢速度上游標查詢比流式查詢更快,流式查詢和普通查詢相比並不能縮短查詢時間;
3、在並行場景下,流式查詢堆記憶體走勢更加穩定,不存在疊加式增加。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45