1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        PageHelper 分頁查詢一直有性能問題你知道嗎?

        共 10362字,需瀏覽 21分鐘

         ·

        2021-02-20 12:18

        作者:歲月安然

        blog.csdn.net/baidu_38083619/article/details/82463058

        前因

        項目一直使用的是PageHelper實現(xiàn)分頁功能,項目前期數(shù)據(jù)量較少一直沒有什么問題。隨著業(yè)務(wù)擴增,數(shù)據(jù)庫擴增PageHelper出現(xiàn)了明顯的性能問題。

        幾十萬甚至上百萬的單表數(shù)據(jù)查詢性能緩慢,需要幾秒乃至十幾秒的查詢時間。故此特地研究了一下PageHelper源碼,查找PageHelper分頁的實現(xiàn)方式。

        一段較為簡單的查詢,跟隨debug開始源碼探尋之旅。

        public?ResultContent?select(Integer?id)?{
        ????????Page?blogPage?=?PageHelper.startPage(1,3).doSelectPage(?()?->?testDao.select(id));
        ????????List?test?=?(List)blogPage.getResult();
        ????????return?new?ResultContent(0,?"success",?test);
        ????}

        主要保存由前端傳入的pageNum(頁數(shù))、pageSize(每頁顯示數(shù)量)和count(是否進行count(0)查詢)信息。

        這里是簡單的創(chuàng)建page并保存當(dāng)前線程的變量副本心里,不做深究。

        public?static??Page?startPage(int?pageNum,?int?pageSize)?{
        ????????return?startPage(pageNum,?pageSize,?DEFAULT_COUNT);
        ????}
        ?
        ????public?static??Page?startPage(int?pageNum,?int?pageSize,?boolean?count)?{
        ????????return?startPage(pageNum,?pageSize,?count,?(Boolean)null,?(Boolean)null);
        ????}
        ?
        ????public?static??Page?startPage(int?pageNum,?int?pageSize,?String?orderBy)?{
        ????????Page?page?=?startPage(pageNum,?pageSize);
        ????????page.setOrderBy(orderBy);
        ????????return?page;
        ????}
        ?
        ????public?static??Page?startPage(int?pageNum,?int?pageSize,?boolean?count,?Boolean?reasonable,?Boolean?pageSizeZero)?{
        ????????Page?page?=?new?Page(pageNum,?pageSize,?count);
        ????????page.setReasonable(reasonable);
        ????????page.setPageSizeZero(pageSizeZero);
        ????????Page?oldPage?=?getLocalPage();
        ????????if(oldPage?!=?null?&&?oldPage.isOrderByOnly())?{
        ????????????page.setOrderBy(oldPage.getOrderBy());
        ????????}
        ?
        ????????setLocalPage(page);
        ????????return?page;
        ????}

        開始執(zhí)行真正的select語句

        public??Page?doSelectPage(ISelect?select)?{
        ????????select.doSelect();
        ????????return?this;
        ????}

        進入MapperProxy類執(zhí)行invoke方法獲取到方法名稱及參數(shù)值

        public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
        ????if?(Object.class.equals(method.getDeclaringClass()))?{
        ??????try?{
        ????????return?method.invoke(this,?args);
        ??????}?catch?(Throwable?t)?{
        ????????throw?ExceptionUtil.unwrapThrowable(t);
        ??????}
        ????}
        ????final?MapperMethod?mapperMethod?=?cachedMapperMethod(method);
        ????return?mapperMethod.execute(sqlSession,?args);
        ??}

        接著是MapperMethod方法執(zhí)行execute語句,判斷是增、刪、改、查。判斷返回值是多個,進入executeForMany方法

        public?Object?execute(SqlSession?sqlSession,?Object[]?args)?{
        ????Object?result;
        ????if?(SqlCommandType.INSERT?==?command.getType())?{
        ??????Object?param?=?method.convertArgsToSqlCommandParam(args);
        ??????result?=?rowCountResult(sqlSession.insert(command.getName(),?param));
        ????}?else?if?(SqlCommandType.UPDATE?==?command.getType())?{
        ??????Object?param?=?method.convertArgsToSqlCommandParam(args);
        ??????result?=?rowCountResult(sqlSession.update(command.getName(),?param));
        ????}?else?if?(SqlCommandType.DELETE?==?command.getType())?{
        ??????Object?param?=?method.convertArgsToSqlCommandParam(args);
        ??????result?=?rowCountResult(sqlSession.delete(command.getName(),?param));
        ????}?else?if?(SqlCommandType.SELECT?==?command.getType())?{
        ??????if?(method.returnsVoid()?&&?method.hasResultHandler())?{
        ????????executeWithResultHandler(sqlSession,?args);
        ????????result?=?null;
        ??????}?else?if?(method.returnsMany())?{
        ????????result?=?executeForMany(sqlSession,?args);
        ??????}?else?if?(method.returnsMap())?{
        ????????result?=?executeForMap(sqlSession,?args);
        ??????}?else?{
        ????????Object?param?=?method.convertArgsToSqlCommandParam(args);
        ????????result?=?sqlSession.selectOne(command.getName(),?param);
        ??????}
        ????}?else?if?(SqlCommandType.FLUSH?==?command.getType())?{
        ????????result?=?sqlSession.flushStatements();
        ????}?else?{
        ??????throw?new?BindingException("Unknown?execution?method?for:?"?+?command.getName());
        ????}
        ????if?(result?==?null?&&?method.getReturnType().isPrimitive()?&&?!method.returnsVoid())?{
        ??????throw?new?BindingException("Mapper?method?'"?+?command.getName()?
        ??????????+?"?attempted?to?return?null?from?a?method?with?a?primitive?return?type?("?+?method.getReturnType()?+?").");
        ????}
        ????return?result;
        ??}

        這個方法開始調(diào)用SqlSessionTemplate、DefaultSqlSession等類獲取到Mapper.xml文件的SQL語句

        private??Object?executeForMany(SqlSession?sqlSession,?Object[]?args)?{
        ????List?result;
        ????Object?param?=?method.convertArgsToSqlCommandParam(args);
        ????if?(method.hasRowBounds())?{
        ??????RowBounds?rowBounds?=?method.extractRowBounds(args);
        ??????result?=?sqlSession.selectList(command.getName(),?param,?rowBounds);
        ????}?else?{
        ??????result?=?sqlSession.selectList(command.getName(),?param);
        ????}
        ????//?issue?#510?Collections?&?arrays?support
        ????if?(!method.getReturnType().isAssignableFrom(result.getClass()))?{
        ??????if?(method.getReturnType().isArray())?{
        ????????return?convertToArray(result);
        ??????}?else?{
        ????????return?convertToDeclaredCollection(sqlSession.getConfiguration(),?result);
        ??????}
        ????}
        ????return?result;
        ??}

        開始進入PageHelper的真正實現(xiàn),Plugin通過實現(xiàn)InvocationHandler進行動態(tài)代理獲取到相關(guān)信息

        public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
        ????try?{
        ??????Set?methods?=?signatureMap.get(method.getDeclaringClass());
        ??????if?(methods?!=?null?&&?methods.contains(method))?{
        ????????return?interceptor.intercept(new?Invocation(target,?method,?args));
        ??????}
        ??????return?method.invoke(target,?args);
        ????}?catch?(Exception?e)?{
        ??????throw?ExceptionUtil.unwrapThrowable(e);
        ????}
        ??}

        PageInterceptor 實現(xiàn)Mybatis的Interceptor 接口,進行攔截

        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;
        ????????????if(args.length?==?4)?{
        ????????????????boundSql?=?ms.getBoundSql(parameter);
        ????????????????cacheKey?=?executor.createCacheKey(ms,?parameter,?rowBounds,?boundSql);
        ????????????}?else?{
        ????????????????cacheKey?=?(CacheKey)args[4];
        ????????????????boundSql?=?(BoundSql)args[5];
        ????????????}
        ?
        ????????????this.checkDialectExists();
        ????????????List?resultList;
        ????????????if(!this.dialect.skip(ms,?parameter,?rowBounds))?{
        ????????????????if(this.dialect.beforeCount(ms,?parameter,?rowBounds))?{
        ????????????????????Long?count?=?this.count(executor,?ms,?parameter,?rowBounds,?resultHandler,?boundSql);
        ????????????????????if(!this.dialect.afterCount(count.longValue(),?parameter,?rowBounds))?{
        ????????????????????????Object?var12?=?this.dialect.afterPage(new?ArrayList(),?parameter,?rowBounds);
        ????????????????????????return?var12;
        ????????????????????}
        ????????????????}
        ?
        ????????????????resultList?=?ExecutorUtil.pageQuery(this.dialect,?executor,?ms,?parameter,?rowBounds,?resultHandler,?boundSql,?cacheKey);
        ????????????}?else?{
        ????????????????resultList?=?executor.query(ms,?parameter,?rowBounds,?resultHandler,?cacheKey,?boundSql);
        ????????????}
        ?
        ????????????Object?var16?=?this.dialect.afterPage(resultList,?parameter,?rowBounds);
        ????????????return?var16;
        ????????}?finally?{
        ????????????this.dialect.afterAll();
        ????????}
        ????}

        轉(zhuǎn)到ExecutorUtil抽象類的pageQuery方法

        public?static??List?pageQuery(Dialect?dialect,?Executor?executor,?MappedStatement?ms,?Object?parameter,?RowBounds?rowBounds,?ResultHandler?resultHandler,?BoundSql?boundSql,?CacheKey?cacheKey)?throws?SQLException?{
        ????????if(!dialect.beforePage(ms,?parameter,?rowBounds))?{
        ????????????return?executor.query(ms,?parameter,?RowBounds.DEFAULT,?resultHandler,?cacheKey,?boundSql);
        ????????}?else?{
        ????????????parameter?=?dialect.processParameterObject(ms,?parameter,?boundSql,?cacheKey);
        ????????????String?pageSql?=?dialect.getPageSql(ms,?boundSql,?parameter,?rowBounds,?cacheKey);
        ????????????BoundSql?pageBoundSql?=?new?BoundSql(ms.getConfiguration(),?pageSql,?boundSql.getParameterMappings(),?parameter);
        ????????????Map?additionalParameters?=?getAdditionalParameter(boundSql);
        ????????????Iterator?var12?=?additionalParameters.keySet().iterator();
        ?
        ????????????while(var12.hasNext())?{
        ????????????????String?key?=?(String)var12.next();
        ????????????????pageBoundSql.setAdditionalParameter(key,?additionalParameters.get(key));
        ????????????}
        ?
        ????????????return?executor.query(ms,?parameter,?RowBounds.DEFAULT,?resultHandler,?cacheKey,?pageBoundSql);
        ????????}
        ????}

        在抽象類AbstractHelperDialect的getPageSql獲取到對應(yīng)的Page對象

        public?String?getPageSql(MappedStatement?ms,?BoundSql?boundSql,?Object?parameterObject,?RowBounds?rowBounds,?CacheKey?pageKey)?{
        ????????String?sql?=?boundSql.getSql();
        ????????Page?page?=?this.getLocalPage();
        ????????String?orderBy?=?page.getOrderBy();
        ????????if(StringUtil.isNotEmpty(orderBy))?{
        ????????????pageKey.update(orderBy);
        ????????????sql?=?OrderByParser.converToOrderBySql(sql,?orderBy);
        ????????}
        ?
        ????????return?page.isOrderByOnly()?sql:this.getPageSql(sql,?page,?pageKey);
        ????}

        進入到MySqlDialect類的getPageSql方法進行SQL封裝,根據(jù)page對象信息增加Limit。分頁的信息就是這么拼裝起來的

        public?String?getPageSql(String?sql,?Page?page,?CacheKey?pageKey)?{
        ????????StringBuilder?sqlBuilder?=?new?StringBuilder(sql.length()?+?14);
        ????????sqlBuilder.append(sql);
        ????????if(page.getStartRow()?==?0)?{
        ????????????sqlBuilder.append("?LIMIT???");
        ????????}?else?{
        ????????????sqlBuilder.append("?LIMIT??,???");
        ????????}
        ?
        ????????return?sqlBuilder.toString();
        ????}

        將最后拼裝好的SQL返回給DefaultSqlSession執(zhí)行查詢并返回

        public??List?selectList(String?statement,?Object?parameter,?RowBounds?rowBounds)?{
        ????try?{
        ??????MappedStatement?ms?=?configuration.getMappedStatement(statement);
        ??????return?executor.query(ms,?wrapCollection(parameter),?rowBounds,?Executor.NO_RESULT_HANDLER);
        ????}?catch?(Exception?e)?{
        ??????throw?ExceptionFactory.wrapException("Error?querying?database.??Cause:?"?+?e,?e);
        ????}?finally?{
        ??????ErrorContext.instance().reset();
        ????}

        至此整個查詢過程完成,原來PageHelper的分頁功能是通過Limit拼接SQL實現(xiàn)的。查詢效率低的問題也找出來了,那么應(yīng)該如何解決。

        推薦閱讀B站面試官,談?wù)凪ySQL是如何解決幻讀問題的?

        首先分析SQL語句,limit在數(shù)據(jù)量少或者頁數(shù)比較靠前的時候查詢效率是比較高的。(單表數(shù)據(jù)量百萬進行測試)

        select?*?from?user?where?age?=?10?limit?1,10;結(jié)果顯示0.43s

        當(dāng)where條件后的結(jié)果集較大并且頁數(shù)達到一個量級整個SQL的查詢效率就十分低下(哪怕where的條件加上了索引也不行)。

        select?*?from?user?where?age?=?10?limit?100000,10;結(jié)果顯示4.73s

        那有什么解決方案呢?mysql就不能單表數(shù)據(jù)量超百萬乃至千萬嘛?答案是NO,顯然是可以的。

        SELECT?a.*?FROM?USER?a
        INNER?JOIN?
        ????(SELECT?id?FROM?USER?WHERE?age?=?10?LIMIT?100000,10)?b?
        ON?a.id?=?b.id;

        結(jié)果0.53s

        完美解決了查詢效率問題?。?!其中需要對where條件增加索引,id因為是主鍵自帶索引。select返回減少回表可以提升查詢性能,所以采用查詢主鍵字段后進行關(guān)聯(lián)大幅度提升了查詢效率。

        PageHelper想要優(yōu)化需要在攔截器的拼接SQL部分進行重構(gòu),由于博主能力有限暫未實現(xiàn)。能力較強的讀者可以自己進行重構(gòu)

        附上PageHelper的git地址:

        https://github.com/pagehelper/Mybatis-PageHelper/

        瀏覽 53
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            骚逼操 | 国产精品免费久久久久软件 | 大陆美女操逼网站 | 三级片久久久久久 | 色999精品 | 噜噜色网 | 狂操狂射美女逼 | 豆花操逼视频 | 国产精品久久久久久久久久春嫩 | 日韩特黄免费视频 |