1.在做項目的時候遇到這樣的問題就是:當數據達到百萬級的時候分頁查詢的速率非常慢,下面個給直觀的現象截圖:測試數據為500W條數據
平時在分頁的時候這么查詢總數的:但是當數據達到百萬級的時候會發現致命問題
SELECT COUNT(*) from test

可以直觀看到查詢時間達到近乎20S,啥意思你懂的,客戶點一下要等待這么長的時間直接導致超時,這是不能容忍的,但是加一點就可以提高10倍查詢速率,下面截圖可以很清晰看到,所以以后在這么寫的要注意別給自己挖坑,當然有其他條件在后面加就可以了

2.本文不是要優化這個問題,而是再次基礎上更加優化,正常分頁都是先查詢數據然后再查詢總數,要查2次,后面介紹查詢一次就可以解決:

(1)建立自己的表,表中達到500W數據左右即可,添加方法很多,我是在idea中寫的代碼加進去的,挺慢的,有好的方法歡迎提出來;
(2)表建立完成后就開始寫代碼了:
本次的優化在於mysql的 SQL_CALC_FOUND_ROWS函數,這個是什么可以自行查閱下相關資料,下面基於mybatias,springboot上代碼講述;
(3)開始之前需要注意,需要在配置文件application.yml的數據源加上 &allowMultiQueries=true如下,加這個是可以執行多條sql不然會報錯!!!
jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
(4)然后就是xml文件,主要這么寫
<sql id="whereCaus">
<if test="name != null">
and name like '%${name}%'
</if>
</sql>
<resultMap id="count" type="java.lang.Integer">
<result column="count" />
</resultMap>
<resultMap id="BaseResultMap" type="com.example.demo.model.Test">
<result column="id" jdbcType="INTEGER" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
</resultMap>
<select id="getListData" resultMap="BaseResultMap,count">
SELECT SQL_CALC_FOUND_ROWS name,id
FROM test
<where>
<include refid="whereCaus"/>
</where>
order by id
LIMIT #{startOff},#{pageSize};
SELECT FOUND_ROWS() as count;
</select>
<select id="getListData2" resultType="com.example.demo.model.Test">
SELECT name,id
FROM test
<where>
<include refid="whereCaus"/>
</where>
order by id
LIMIT #{startOff},#{pageSize};
</select>
<select id="getTotal" resultType="java.lang.Integer">
select count(id) from test where id >0
<include refid="whereCaus"/>
</select>
<select id="getTotal2" resultType="java.lang.Integer">
select count(id) from test
<where>
<include refid="whereCaus"/>
</where>
</select>
dao層:(getTotal2這邊就不做測試了,前面試過了執行時間達到20S可能更久肯定不行的,可以自行試試)
List<?> getListData(ParamsDto paramsDto);//返回類型必須這么寫,否則會出問題
int getTotal(String name);
List<Test> getListData2(ParamsDto paramsDto);
controller層會解析數據,ParamsDto參數對象的話就3個參數private String name;private Integer pageSize;private Integer startOff;自行構建即可
package com.example.demo.controller;
import com.example.demo.dto.ParamsDto;
import com.example.demo.mapper.TestMapper;
import com.example.demo.model.Test;
import com.example.demo.run.RunScan;
import com.example.demo.utils.JsonResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* <p>
* 前端控制器
* </p>
*
* @author lxp
* @since 2021-12-29
*/
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private TestMapper testMapper;
@RequestMapping("/v1.do")
public JsonResult test1(ParamsDto paramsDto) {
Integer currentPage = paramsDto.getStartOff();
//每頁顯示數量
Integer pageSize = paramsDto.getPageSize();
paramsDto.setName(paramsDto.getName());
paramsDto.setPageSize(pageSize);
paramsDto.setStartOff((currentPage-1)*pageSize);
long startTime = System.currentTimeMillis(); //獲取開始時間
List<?> listData = testMapper.getListData(paramsDto);
long endTime = System.currentTimeMillis(); //獲取結束時間
//接收count數據
Integer totalCount = ((List<Integer>) listData.get(1)).get(0);
Integer totalPage = totalCount % pageSize == 0 ?
totalCount / pageSize :
totalCount / pageSize + 1;
List<Test> studentManageVoList = (List<Test>)listData.get(0);
Map map = new HashMap();
map.put("listData",studentManageVoList);
map.put("totalPage",totalPage);
map.put("finishTime",(endTime - startTime) + "ms");
return JsonResult.successResult(map);
}
@RequestMapping("/v2.do")
public JsonResult test2(ParamsDto paramsDto){
Integer currentPage = paramsDto.getStartOff();
//每頁顯示數量
Integer pageSize = paramsDto.getPageSize();
paramsDto.setName(paramsDto.getName());
paramsDto.setPageSize(pageSize);
paramsDto.setStartOff((currentPage-1)*pageSize);
long startTime = System.currentTimeMillis(); //獲取開始時間
List<Test> listData2 = testMapper.getListData2(paramsDto);
int totalCount = testMapper.getTotal(paramsDto.getName());
long endTime = System.currentTimeMillis(); //獲取結束時間
Integer totalPage = totalCount % pageSize == 0 ?
totalCount / pageSize :
totalCount / pageSize + 1;
Map map = new HashMap();
map.put("listData",listData2);
map.put("totalPage",totalPage);
map.put("finishTime",(endTime - startTime) + "ms");
return JsonResult.successResult(map);
}
}
可以對比下這兩個方法的優劣,測試的結果顯而易見/test/v1.do的接口速度比/test/v2.do速度要快,差不多2倍速度
下面實驗下對比這兩個接口訪問時間:


就此可以得到簡單結論,使用SELECT SQL_CALC_FOUND_ROWS * .... ;SELECT FOUND_ROWS() as count;相對於平時使用select count(*)查詢總數+查詢數據效率上快上快1倍的速度,分頁到后面的越明顯,分頁靠前的可能差不多,測試的時候可以自行實驗,當然2S的時間要是還能在優化就更好了,小伙伴有更加好的建議可以一起探討,感謝觀看!!!
