開發(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
有些我沒有嘗試,有些說增加索引的,他們多少都不太適合我現(xiàn)在的環(huán)境,一是索引會增加數(shù)據(jù)冗余,二是我沒線上數(shù)據(jù)庫的權限,不好測試,而且大表增加索引會很慢,三是即使使用正確的索引,索引也會很大。所以我使用了最簡單粗暴的。
先放上一個優(yōu)化前后的速度對比:

舊版本

去掉后
接著是具體的工作,如何最小改動現(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;
}
例如當前page為1,pageSize為20,如果有100條數(shù)據(jù)的話,返回的total就是21,這時候前端bootstrap-table自然就會顯示兩個頁面,當你點擊第二個頁面時,返回就是41,這時候前端又會顯示第三個頁面。
效果如下


不過很明顯的是,這里的總共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)計,換上一個自定義按鈕,然后綁定上事件。當前有些值時通過外界傳來的,方便擴展。
最后,上一下最終效果

最終達到了低入侵的優(yōu)化效果,只需要在展示慢的表格上分頁時指定count為false即可。
結尾
當初之所以冒出這個想法是因為之前看過的一些討論,有些 to c 的網(wǎng)站其實都沒有進行 count 總數(shù)查詢的,也不允許用戶查詢太老的數(shù)據(jù),一是分頁的頁碼太大,即使有索引也會太慢,更何況還有 order排序這種場景,二是可能他們會把太老的數(shù)據(jù)給存檔,遷移到別的地方,或者分庫分表等,允許用戶隨意查看勢必會增加架構上的復雜性。去掉了 count 這次查詢之后,起碼會提高 50% 的性能,如果需要總數(shù)的話,還可以進行手動點擊獲取。
在和我組長討論了一下后,確定了業(yè)務上并沒有必要,所以就有了這個實踐。
感謝你看到這里,希望我的想法能給你帶來幫助,有興趣的話也歡迎關注我的其他平臺。