上线到凌晨4点半 pagehelper的bug?
大家好,我是烤鴨:
????上上周末上線到凌晨4點(diǎn)半,哭了,沒想到問題竟然如此簡(jiǎn)單。最近又懶惰了,寫了開頭就一直放著了,今天終于補(bǔ)上。
?
問題日志
Error querying database. Cause: com.github.pagehelper.PageException: 被分頁(yè)的語(yǔ)句已經(jīng)包含了Top,不能再通過分頁(yè)插件進(jìn)行分頁(yè)查詢!奇怪的是報(bào)錯(cuò)的語(yǔ)句并沒有使用分頁(yè)插件,只是一個(gè)簡(jiǎn)單的查詢。
原因猜想
遇到問題第一時(shí)間回滾之后就好了,說(shuō)明問題出現(xiàn)在這次提交。
跟pagehelper有關(guān)。
- 修改pom文件,更改 pagehelper 相關(guān)依賴,導(dǎo)致的問題。并不是。
- 修改了 pagehelper 的配置(配置類或者是yml)。也沒有。
- 排除法,針對(duì)此次提交的代碼進(jìn)行部分上線。
問題復(fù)現(xiàn)
最后發(fā)現(xiàn)確實(shí)有個(gè)地方,寫法是這樣的。
由于項(xiàng)目拆分,把原來(lái)直接查庫(kù)的地方改成了http調(diào)用,但是改寫的人并沒有關(guān)注業(yè)務(wù)邏輯,導(dǎo)致分頁(yè)代碼沒有注釋。(雖然注釋了也不對(duì),http改寫的方法也沒支持分頁(yè)參數(shù))
PageHelper.startPage(pageNum,pageSize); //注釋dao,改為http調(diào)用 //xxxDao.selectXxx(); xxxHttp.select();就這樣分頁(yè)在當(dāng)前的線程沒有使用,當(dāng)這個(gè)線程執(zhí)行別的Sql 查詢的時(shí)候,就會(huì)有問題,具體看下下面的源碼分析。
源碼分析
我們的數(shù)據(jù)庫(kù)是SqlServer,Mysql和SqlServer的分頁(yè)實(shí)現(xiàn)是不一樣的。
下面的演示項(xiàng)目里分別模擬了不同的場(chǎng)景。
主要還是看下分頁(yè)和SqlServer的實(shí)現(xiàn)源碼。
分頁(yè)插件整個(gè)核心代碼是在 PageInterceptor 這個(gè)攔截器里。
執(zhí)行 PageHelper.startPage(pageNum,pageSize); 時(shí)會(huì)將分頁(yè)參數(shù)放到TheadLocal里。
PageMethod
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {Page<E> page = new Page<E>(pageNum, pageSize, count);page.setReasonable(reasonable);page.setPageSizeZero(pageSizeZero);//當(dāng)已經(jīng)執(zhí)行過orderBy的時(shí)候Page<E> oldPage = getLocalPage();if (oldPage != null && oldPage.isOrderByOnly()) {page.setOrderBy(oldPage.getOrderBy());}setLocalPage(page);return page; }finally里執(zhí)行的AfterAll()代碼就是在執(zhí)行之后清空 ThreadLocal
public Object intercept(Invocation invocation) throws Throwable {try {Object[] args = invocation.getArgs();MappedStatement ms = (MappedStatement) args[0];Object parameter = args[1];RowBounds rowBounds = (RowBounds) args[2];ResultHandler resultHandler = (ResultHandler) args[3];Executor executor = (Executor) invocation.getTarget();CacheKey cacheKey;BoundSql boundSql;//由于邏輯關(guān)系,只會(huì)進(jìn)入一次if (args.length == 4) {//4 個(gè)參數(shù)時(shí)boundSql = ms.getBoundSql(parameter);cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);} else {//6 個(gè)參數(shù)時(shí)cacheKey = (CacheKey) args[4];boundSql = (BoundSql) args[5];}checkDialectExists();List resultList;//調(diào)用方法判斷是否需要進(jìn)行分頁(yè),如果不需要,直接返回結(jié)果if (!dialect.skip(ms, parameter, rowBounds)) {//判斷是否需要進(jìn)行 count 查詢if (dialect.beforeCount(ms, parameter, rowBounds)) {//查詢總數(shù)Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql);//處理查詢總數(shù),返回 true 時(shí)繼續(xù)分頁(yè)查詢,false 時(shí)直接返回if (!dialect.afterCount(count, parameter, rowBounds)) {//當(dāng)查詢總數(shù)為 0 時(shí),直接返回空的結(jié)果return dialect.afterPage(new ArrayList(), parameter, rowBounds);}}resultList = ExecutorUtil.pageQuery(dialect, executor,ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);} else {//rowBounds用參數(shù)值,不使用分頁(yè)插件處理時(shí),仍然支持默認(rèn)的內(nèi)存分頁(yè)resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);}return dialect.afterPage(resultList, parameter, rowBounds);} finally {if(dialect != null){dialect.afterAll();}} }復(fù)現(xiàn)SqlServer報(bào)錯(cuò)信息:
Mysql的查詢時(shí)也會(huì)報(bào)limit的錯(cuò)
SqlServerParser,報(bào)錯(cuò)語(yǔ)句出現(xiàn)在這
根據(jù)源碼看到會(huì)先生成這樣的語(yǔ)句,再根據(jù)傳入的number和size進(jìn)行替換
SELECT TOP 9223372036854775807 user_no FROM (SELECT ROW_NUMBER() OVER (ORDER BY RAND()) PAGE_ROW_NUMBER, user_no FROM (SELECT user_no FROM dbo.[user]) AS PAGE_TABLE_ALIAS) AS PAGE_TABLE_ALIAS WHERE PAGE_ROW_NUMBER > -9223372036854775808 ORDER BY PAGE_ROW_NUMBER上面的源碼是針對(duì)SqlServer分頁(yè)時(shí)的,其實(shí)打個(gè)斷點(diǎn)跟一下就行。
演示項(xiàng)目地址
https://gitee.com/fireduck_admin/pagehelper-maggie-demo
總結(jié)
為什么這個(gè)問題會(huì)折騰到4點(diǎn),測(cè)試環(huán)境和本地環(huán)境都沒有復(fù)現(xiàn),其中一個(gè)主要原因就是流量不夠,可能一兩個(gè)報(bào)錯(cuò)信息會(huì)被忽略,導(dǎo)致排查問題難度加大。
再加上不是測(cè)試回歸的重點(diǎn),以后針對(duì)上線的代碼還是要加強(qiáng)驗(yàn)證。
總結(jié)
以上是生活随笔為你收集整理的上线到凌晨4点半 pagehelper的bug?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 部署和调优 1.3 pureftp部署
- 下一篇: 性能