大表的展示優(yōu)化

開發(fā)中可能會遇到一些數(shù)據(jù)量非常大的表,需要前臺進行展示,無論怎么優(yōu)化都非常慢。比如訂單表,日志表。這里分享一下我的解決方案。就是

'避免使用 count'

我們常見的企業(yè)后端表格一個查詢時都會分頁,然后計算頁數(shù),展示在表格下面,然而我們很少需要查看每一頁,或者跳轉到最后幾頁,尤其是這種數(shù)據(jù)量特別大的表,我們通常是粗略瀏覽,或者根據(jù)搜索條件搜索我們指定的數(shù)據(jù),或者導出數(shù)據(jù)。這時候查詢是進行count計算頁碼就是多余的,而且會消耗巨大的資源。

當前,如果你業(yè)務必需要這么做,那也沒辦法,我下面寫的內容可能就不適合你。

首先,這里我說一下我的相關開發(fā)框架。后端是 spring+mybatis+pagehelper,前段是bootstrap``jquery,數(shù)據(jù)庫為mysql ,如果你的方案和我不同也沒關系,思路是大致相通的。


剛開始的時候,我也去找了其他的解決的方案,效果不盡如人意,治標不治本

參考鏈接

https://stackoverflow.com/questions/22352073/improve-innodb-count-performance

https://stackoverflow.com/questions/57921400/are-full-count-queries-really-so-slow-on-a-large-mysql-innodb-tables

有些我沒有嘗試,有些說增加索引的,他們多少都不太適合我現(xiàn)在的環(huán)境,一是索引會增加數(shù)據(jù)冗余,二是我沒線上數(shù)據(jù)庫的權限,不好測試,而且大表增加索引會很慢,三是即使使用正確的索引,索引也會很大。所以我使用了最簡單粗暴的。

先放上一個優(yōu)化前后的速度對比:

old

舊版本

new

去掉后

接著是具體的工作,如何最小改動現(xiàn)在的代碼來優(yōu)化性能。

代碼基于 ruoyi框架

在分頁這里,增加了一個入?yún)磉x擇是否 進行count,默認為true,只在幾個大表默認關閉count查詢,

在關閉了count的同時,我會多查一條數(shù)據(jù),以此來判定有沒有下一頁

protected void startPage(boolean count) {
    PageDomain pageDomain = TableSupport.buildPageRequest();
    Integer pageNum = pageDomain.getPageNum();
    Integer pageSize = pageDomain.getPageSize();
    if (pageNum == null || pageSize == null) {
        return;
    }
    String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
    if (count) {
        PageHelper.startPage(pageNum, pageSize,orderBy);
    } else {
        //不進行count的話手動計算邊界,并且頁數(shù)+1,來計算有沒有下一頁
        int startRow = pageNum > 0 ? (pageNum - 1) * pageSize : 0;
        PageHelper.offsetPage(startRow, pageSize+1,false).setOrderBy(orderBy);
    }
}

在后面獲取分頁數(shù)據(jù)并且發(fā)送給前端的時候,根據(jù)是否進行過count查詢來返回不同的值,

protected TableDataInfo getDataTable(List<?> list) {
        TableDataInfo rspData = new TableDataInfo();
        rspData.setCode(0);
        rspData.setRows(list);
        if (list instanceof Page) {
            Page<?> page = (Page) list;
            rspData.setTotal(page.getTotal());
            //如果沒有分頁
            rspData.setCount(page.isCount());
            if (!page.isCount()) {
                rspData.setTotal(page.getStartRow() + list.size());
            }
        } else {
            rspData.setTotal(list.size());
        }
        return rspData;
    }

例如當前page1pageSize20,如果有100條數(shù)據(jù)的話,返回的total就是21,這時候前端bootstrap-table自然就會顯示兩個頁面,當你點擊第二個頁面時,返回就是41,這時候前端又會顯示第三個頁面。

效果如下

image-20220209200943262
image-20220209201008061

不過很明顯的是,這里的總共21,41條數(shù)據(jù)會對用戶造成困擾,所以我打算隱藏這個值,并且加上了一個查詢總數(shù)的按鈕,當需要獲取總共條數(shù)時,可以手動獲取。

所以,我又寫了個jquery插件。

/**
 * bootstrap-table的擴展,適配沒有count計數(shù)的情況
 */
(function ($) {
    'use strict';
    $.extend($.fn.bootstrapTable.defaults, {
        /**
         * 分頁顯示總數(shù)按鈕的回調,應該返回一個Promise<Number>或者Number
         * 如果為null,則不顯示顯示總數(shù)按鈕
         * @type {function (): Promise<Number>| Number}
         */
        onPageShowTotal: null
    });


    let BootstrapTable = $.fn.bootstrapTable.Constructor;
    let _initPagination = BootstrapTable.prototype.initPagination;

    // 擴展已有的初始化分頁組件的方法
    BootstrapTable.prototype.initPagination = function () {
        _initPagination.apply(this, Array.prototype.slice.apply(arguments));
        if (this.options.pageHasCount) {
            //如果已經(jīng)有count計數(shù),跳過后續(xù)邏輯
            return;
        }
        //如果沒有count計算,修改界面展示
        let paginationInfo = this.$pagination.find(".pagination-info");
        let countTxt = paginationInfo.text();
        let showTotal = this.options.onPageShowTotal ? ",<a class='show-total'>顯示總數(shù)</a> " : "";
        paginationInfo.html(countTxt.split(",")[0] + showTotal);

        //分頁大小下拉框顯示全部
        let pageSizeSelector = this.$pagination.find(".dropdown-menu");
        let pageList = this.options.pageList;
        let pageSizeSelectorHtml = "";
        pageList.forEach(num => {
            pageSizeSelectorHtml += `<li role="menuitem" ${num == this.options.pageSize ? "class='active'" : ""}><a href="#">${num}</a></li>`;
        });
        pageSizeSelector.html(pageSizeSelectorHtml);
        this.$pagination.find('.show-total').off('click').on('click', $.proxy(this.onPageShowTotal, this));
    };

    /**
     * 點擊顯示總數(shù)之后
     */
    BootstrapTable.prototype.onPageShowTotal = function () {
        let paginationInfo = this.$pagination.find(".pagination-info");
        let value = this.options.onPageShowTotal();
        if (value instanceof Promise) {
            value.then(count => {
                this.$pagination.find(".show-total").remove()
                let countTxt = paginationInfo.text();
                paginationInfo.html(countTxt + `總共 ${count} 條記錄`);
            })
        } else {
            this.$pagination.find(".show-total").remove()
            let countTxt = paginationInfo.text();
            paginationInfo.html(countTxt + `總共 ${value} 條記錄`);
        }

    }
})(jQuery);

這里代碼就不做太多的解釋了,還要感謝這位老哥的博客,照著他的代碼寫了一個插件。大概就是在原代碼初始化頁碼之后,刪除原先的統(tǒng)計,換上一個自定義按鈕,然后綁定上事件。當前有些值時通過外界傳來的,方便擴展。

最后,上一下最終效果

image-20220209201646265

最終達到了低入侵的優(yōu)化效果,只需要在展示慢的表格上分頁時指定countfalse即可。


結尾

當初之所以冒出這個想法是因為之前看過的一些討論,有些 to c 的網(wǎng)站其實都沒有進行 count 總數(shù)查詢的,也不允許用戶查詢太老的數(shù)據(jù),一是分頁的頁碼太大,即使有索引也會太慢,更何況還有 order排序這種場景,二是可能他們會把太老的數(shù)據(jù)給存檔,遷移到別的地方,或者分庫分表等,允許用戶隨意查看勢必會增加架構上的復雜性。去掉了 count 這次查詢之后,起碼會提高 50% 的性能,如果需要總數(shù)的話,還可以進行手動點擊獲取。

在和我組長討論了一下后,確定了業(yè)務上并沒有必要,所以就有了這個實踐。

感謝你看到這里,希望我的想法能給你帶來幫助,有興趣的話也歡迎關注我的其他平臺。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容