【死磕 Java 基礎】— 我同事一個 select 分頁語句查出來了 3000W 條資料

chenssy發表於2021-08-22

大家好,我是大明哥,一個專注於【死磕 Java】系列創作的男人

個人網站:https://www.cmsblogs.com/。專注於 Java 優質系列文章分享,提供一站式 Java 學習資料


某天我正在工位上聽著 Vicotry,愉快地敲著 hello world ,這感覺就像我寫的程式碼能征服世界。突然運維給我打了一個電話,說我們某臺伺服器 OOM 了,要我過去看下,這感覺就像 xxx,你懂的。

去運維室、登入伺服器、檢視日誌、....一頓操作猛如虎,看到一個 List 物件 600MB +(原諒我們伺服器 low,運維比較小氣,就給 1C2G 的伺服器),檢查當時的 SQL 語句,一看,我的乖乖,將近 4000w + 條資料。我的第一感覺就是,難道又是哪個業務在匯出大批量資料?但是我們所有的 Excel 匯出資料都做了校驗,資料量大於 5w 條就後臺分批次匯出(所以有時候你要慶幸伺服器 low,因為伺服器 low,你就需要進行各種各樣的優化,所有大資料量的操作都需要想辦法優化,所以我們這個應用就有了各種有意思的騷操作,後面有機會分享下)。難道沒有控制住?看了日誌並沒有發現大資料量的 Excel 匯出,所以可以斷定是分頁的地方沒有分頁。看程式碼在一個 if 語句裡面找到了坑,如下:

PageHelper.startPage(queryDTO.getPage(), queryDTO.getLimit());

Page<UserDTO> page;
if (isWitchFlag()) {
    page = userMapper.selectUserList(queryDTO);
}

isWitchFlag() :

    private boolean isWitchFlag() {
        String witchFlag = systemConfigMapper.selectSwitchFlag("key");
        return "1".equals(witchFlag);
    }

對 PageHelper 不是很熟悉的人一定不知道這個坑在哪裡!在 PageHelper 使用文件(https://pagehelper.github.io/faq/)中第一句就闡述了:

只有緊跟在 PageHelper.startPage 方法後的第一個 Mybatis 的查詢(Select)方法會被分頁。。請注意關鍵詞緊跟。為什麼要緊跟呢?因為 PageHelper 的分頁原理使用了 ThreadLocal,他的分頁引數和執行緒是繫結在一起的,當我們執行 PageHelper.startPage() 語句時,他會將分頁引數繫結到 ThreadLocal 中:

setLocalPage()

在攔截器 PageInterceptor 中,最後的 finally 會將 Page 分頁資訊給 remove 掉:

所以,上面那段程式碼的分頁資訊被 if 語句中的 select 查詢語句給消耗掉了,下面真正需要分頁的語句當然就不會執行分頁資訊啦。怎麼解決?兩種方案:

  • 治標不治本:將 PageHelper.startPage() 挪到 if 語句裡面,讓真正的查詢語句緊挨著它。這種方案不治本的原因在於,如果又有小夥伴不知道這個坑,有可能又會踩。
  • 治標治本:使用 Function Lamdba 表示式。

使用 Function Lamdba 來將 PageHelper.startPage() 與分頁查詢語句緊挨在一起,規避掉這個坑

首先我們需要定義一個 PageHelperTool,該 PageHelperTool 是封裝了分頁語句:

@Builder
public class PageHelperTool<P,R> {
    private final Function<P, Page<R>> pageFunction;

    public Page<R> getPageInfo(P request) {
        PageHelper.startPage(((PageRequest)request).getPage(),((PageRequest)request).getLimit());
        return pageFunction.apply(request);
    }
}

然後將分頁的地方全部替換為 PageHelperTool 就可以了:

        Page<UserDTO> page;
        if (isWitchFlag()) {
            PageHelperTool<QueryDTO,UserDTO> pageHelperTool = PageHelperTool.<QueryDTO,UserDTO>builder()
                                                            .pageFunction(userMapper::selectUserList)
                                                            .build();
            
            page = pageHelperTool.getPageInfo(queryDTO);
        }

這樣就可以徹底解決因誤用 PageHelper 導致分頁失效的問題了。

最後一句話:注意看文件啊!!!!!!

相關文章