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的時間要是還能在優化就更好了,小伙伴有更加好的建議可以一起探討,感謝觀看!!!