【開源項目】震驚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嗎?

------------------------------------> 待續

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章