【開源專案】震驚JDBC查詢比MyBatis查詢慢
震驚JDBC查詢比MyBatis查詢快?
文章編寫起始原因,在編寫專案的時候碰到一個深坑,JDBC獲取5000條資料,居然耗時261s,MyBatis同樣的操作,耗時12s左右,震驚。看到這裡下巴都快掉下來了。不是網上都說MyBatis就是封裝了的JDBC嗎?不是都說JDBC比MyBatis快很多很多嗎?為啥這裡卻別這麼大?
先上圖,看看查詢的具體情況吧
具體的程式碼如下:
private List<String> selectTransferData() { List<String> list = new ArrayList<>(); ResultSet rs; Statement stmt; Connection newConn; try { newConn = druidDataSource.getConnection(); stmt = newConn.createStatement(); stmt.setFetchSize(Integer.MIN_VALUE); rs = stmt.executeQuery(sql); } catch (SQLException e) { logger.error("traceId: {}, mysql executeQuery error!!! error msg: {}", traceId, e.getMessage()); throw new DataMigrationException(DataMigrationConstant.MYSQL_EXECUTE_QUERY_ERROR); } String[] cloumnSplit = transferInsertConfig.getTableCloumnName().split(","); try { while (rs.next()) { StringBuilder data = new StringBuilder("("); for (int i = 0; i < cloumnSplit.length; i++) { String cloumnName = cloumnSplit[i].replaceAll(" ", ""); String string = rs.getString(cloumnName); data.append(string).append(", "); } String dataStr = data.substring(0, data.length() - 2) + ")\r\n"; list.add(dataStr); } } catch (Exception e) { logger.error("traceId: {}, 獲取查詢結果集異常!!! error msg: {}", traceId, e.getMessage()); throw new DataMigrationException("獲取查詢結果集異常, 異常資訊:" + e.getMessage()); } finally { try { if (rs != null) { rs.close(); } if (stmt != null) { stmt.close(); } if (newConn != null) { newConn.close(); } } catch (Exception e) { logger.error("traceId: {}, close io error!!! error msg: {}", traceId, e.getMessage()); } } return list; }
程式碼裡面執行的SQL如下:
SELECT * FROM books WHERE id > 3000000 LIMIT offset, length;
-- offset: 150000 start
-- length: 5000
由於程式碼執行過慢,這裡對程式碼進行了新增日誌觀察,看看到底慢在了哪裡。根據網上查詢的資料來看,應該就是慢在了SQL,實際跟蹤下來的結果如下:
關鍵程式碼 | 執行時間 | 預期結果 | 實際結果 | 備註 |
---|---|---|---|---|
newConn = druidDataSource.getConnection(); | 30ms | 快 | 很快 | 符合預期結果 |
rs = stmt.executeQuery(sql); | 12.6s | 慢 | 可以接收的慢 | 不符合260s的預期 |
while (rs.next()) { ... } | 252s | 快 | 慢 | 超出預料了 |
根據這個結果,又在網上找了很久,基本總結下來就就是SQL慢,rs.next()慢是錯覺。然後博主又做了10組sql的查詢結果如下:
SQL | 執行時間 | 表資料量 |
---|---|---|
SELECT * FROM books WHERE id > 3000000 LIMIT 300000, 5000 | 12.576s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 305000, 5000 | 12.879s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 310000, 5000 | 15.952s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 315000, 5000 | 15.541s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 320000, 5000 | 13.110s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 325000, 5000 | 12.691 | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 330000, 5000 | 16.411s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 335000, 5000 | 11.809s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 340000, 5000 | 9.461s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 345000, 5000 | 10.374s | > 350w |
直接執行SQL和上面日誌跟蹤到的執行時間出入不大,於是準備使用mybaits來看測試看看,測試程式碼如下
IntStream.range(1, 11).forEach(it -> {
long l = System.currentTimeMillis();
booksMapper.getLimit(it * 5000 + 300000);
long l1 = System.currentTimeMillis();
System.out.println("耗時:" + (l1 - l) / 1000 + "s");
});
List<Books> getLimit(int offset);
<select id="getLimit" resultMap="BaseResultMap" parameterType="int">
SELECT * FROM books WHERE id > 3000000 limit #{offset}, 5000
</select>
執行結果如下:
SQL | 執行時間 | 表資料量 |
---|---|---|
SELECT * FROM books WHERE id > 3000000 LIMIT 305000, 5000 | 10.301s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 310000, 5000 | 11.130s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 315000, 5000 | 12.417s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 320000, 5000 | 13.588s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 325000, 5000 | 13.810s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 330000, 5000 | 13.968s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 335000, 5000 | 18.056s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 340000, 5000 | 11.202s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 345000, 5000 | 18.017s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 350000, 5000 | 16.619s | > 350w |
看到這裡的時候跟JDBC對比,發現如果僅僅只是SQL執行跟MyBatis比較,真的沒有很大出入。但是MyBatis其實不僅查詢了資料,而且還做了資料的封裝並且返回。JDBC這裡也是做了封裝的,為啥差別這麼大。真的是程式碼問題嗎?好像結果已經不言而喻了。博主還是不太敢相信,決定在調整一下程式碼,調整程式碼如下:
while (rs.next()) {
// 刪除了處理邏輯,這樣就不會是封裝返回值影響了
}
實際呼叫結果如下:
執行第幾次 | 執行時間 | 表資料量 |
---|---|---|
1 | 200s | > 350w |
2 | 180s | > 350w |
3 | 165s | > 350w |
4 | 185s | > 350w |
5 | 193s | > 350w |
6 | 184s | > 350w |
7 | 178s | > 350w |
8 | 162s | > 350w |
9 | 180s | > 350w |
10 | 191s | > 350w |
這是不是已經鐵的事實,就是拼接問題?不見得吧。比較下來,拼接問題看來並不是很大。耗時還是再rs.next上。
rs.next()的實現就是游標的移動,游標的移動僅僅只是位置的偏移而已,並不產生很多的消耗。這個時候只能提出假設了,假設rs.next並不僅僅是走了游標而且還在資料庫內做了什麼,並且與我們的SQL有關,那我們SQL本身很慢的話,那這種現狀就有解釋。於是對SQL調整,調整如下:
SELECT * FROM books a INNER JOIN( SELECT * FROM books WHERE id > 3000000 LIMIT 255000, 5000 ) b ON a.id = b.id
先SQL執行測試10組試試:
執行結果如下:
SQL | 執行時間 | 表資料量 |
---|---|---|
SELECT * FROM books WHERE id > 3000000 LIMIT 305000, 5000 | 4.662s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 310000, 5000 | 4.966s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 315000, 5000 | 4.407s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 320000, 5000 | 4.276s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 325000, 5000 | 4.199s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 330000, 5000 | 4.422s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 335000, 5000 | 4.282s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 340000, 5000 | 3.819s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 345000, 5000 | 3.813s | > 350w |
SELECT * FROM books WHERE id > 3000000 LIMIT 350000, 5000 | 3.666s | > 350w |
看起來快了比之前SQL執行快了兩倍還要多。不過這裡有個問題,我們執行SQL的時候,如果是在客戶端裡面執行的,他會自動限定查詢的返回結果,比如:固定顯示200條,其實就是limit 200,這個條件導致我們測試不是那麼的準。所以還是繼續走程式
先將之前的程式碼恢復到最開始的狀態,然後用這個調整的SQL走10組試試
執行第幾次 | 執行時間 | 表資料量 |
---|---|---|
1 | 629.969s | > 350w |
2 | 607.075s | > 350w |
3 | 660.476s | > 350w |
4 | 653.261s | > 350w |
5 | 679.521s | > 350w |
6 | 611.962s | > 350w |
7 | 598.981s | > 350w |
8 | 604.788s | > 350w |
9 | 651.673s | > 350w |
10 | 650.522s | > 350w |
事實證明,就是優化的SQL在客戶端執行看似有用,實際程式內執行,慢了3倍。
JDBC查詢比MyBatis慢在哪裡?
真的是rs.next嗎?
------------------------------------> 待續