为了解决count查询慢而写的分页查询总量缓存器

项目上出现了分页查询接口慢的问题,慢的原因是表数据量大,count查询很慢。

分页查询接口有两次查询:

  • 一次查分页数据,比如15条:

    sql 复制代码
    SELECT * FROM sys_user LIMIT 1,15;
  • 另一次查数据总量:

    sql 复制代码
    SELECT COUNT(*) FROM sys_user;

count查询需要一行行读数据,累加起来得到总量,效率很低。

要解决count查询慢问题,一般有以下几种方案:

  • 优化count查询语句,走索引、减少JOIN表

    一行语句,没有可优化空间,pass。

  • 分页接口的两次查询用多线程同时进行,节省一点时间

    查询LIMIT分页数据,靠前的数据一般都不慢,多线程增加代码复杂性,而且目前的问题是count耗时长,所以该方案也pass。

  • 第一次查询后缓存起来,设置一个合适的过期时间,缓存期间可以快速响应

    简单可行,缺点是数据实时性不高,新增、删除的数量短期内不会更新,不适合用在高实时性的场景下。

  • 使用EXPLAIN获取的预估行数

    响应快,其原理是根据数据总大小和采样的行数据大小做计算得出的预估行数,这是一个近似值,不适合要求高精确度的场景。

  • 转移数据到其他查询快的数据库,如Elasticsearch、ClickHouse

    既然MySQL不中用,那就换其他的存储引擎,虽然解决了查询慢的问题,但是工作量激增。

本文选择的是查询缓存的方案,因为不要求高实时性。

代码编写

现在编写代码,写一个分页数量缓存工具类:PageCountCacheManager

实现内容:

提供缓存接口函数,支持设置缓存条件:查询时间超过指定值、查询数量超过指定值,当满足任一条件即可触发缓存机制。

直接贴代码:

java 复制代码
import com.cc.utils.CacheUtil;

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

/**
 * 分页查询的数据总量缓存器
 *
 * @author cc
 * @date 2023-09-18 10:28
 */
public class PageCountCacheManager {

    /**
     * 查询时间超过这个值就缓存,单位(秒)
     */
    private static final long TIME = 5;

    /**
     * 查询数量超过这个值就缓存
     */
    private static final long AMOUNT = 100000;

    /**
     * 缓存过期时间,单位(秒)
     */
    private static final long EXPIRE_TIME = 60;

    /**
     * 按查询时间来缓存,超过一定时间的查询就缓存起来
     */
    public static long cacheByTime(String key, Callable<Long> task) {
        key = "PageCount_Time_" + key;
        return run(key, task, true, false, false, TIME, 0, EXPIRE_TIME);
    }

    public static long cacheByTime(String key, Callable<Long> task, long time, long expireTime) {
        key = "PageCount_Time_" + key;
        return run(key, task, true, false, false, time, 0, expireTime);
    }

    /**
     * 按查询数量来缓存,超过一定数量的查询就缓存起来
     */
    public static long cacheByAmount(String key, Callable<Long> task) {
        key = "PageCount_Amount_" + key;
        return run(key, task, false, true, false, 0, AMOUNT, EXPIRE_TIME);
    }

    public static long cacheByAmount(String key, Callable<Long> task, long amount, long expireTime) {
        key = "PageCount_Amount_" + key;
        return run(key, task, false, true, false, 0, amount, expireTime);
    }

    /**
     * 结合查询时间和查询数量来缓存,当超过一定时间或一定数量就缓存起来
     */
    public static long cacheByBoth(String key, Callable<Long> task) {
        key = "PageCount_Both_" + key;
        return run(key, task, false, false, true, TIME, AMOUNT, EXPIRE_TIME);
    }

    public static long cacheByBoth(String key, Callable<Long> task, long time, long amount, long expireTime) {
        key = "PageCount_Both_" + key;
        return run(key, task, false, false, true, time, amount, expireTime);
    }

    /*************** 私有方法 ***************/
    private static long run(String key, Callable<Long> task, boolean byTime, boolean byAmount, boolean byBoth, long time, long amount, long expireTime) {
        long start = System.currentTimeMillis();

        // 查数据
        Long result = null;
        try {
            result = task.call();
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 查询结果为空,令其为0
        if (result == null) {
            result = 0L;
        }

        if (byTime || byBoth) {
            // 判断时间
            long executionTime = System.currentTimeMillis() - start;
            if (executionTime > time) {
                // 缓存数据
                CacheUtil.set(key, result, expireTime, TimeUnit.SECONDS);
            }
        }

        if (byAmount || byBoth) {
            // 判断数量
            if (result > amount) {
                // 缓存数据
                CacheUtil.set(key, result, expireTime, TimeUnit.SECONDS);
            }
        }
        return result;
    }
}

其中CacheUtil是缓存类,可以是JVM内缓存,也可以是RedisTemplate缓存,这个依项目而定。

使用方式:

java 复制代码
// 这是Service的查询函数
@Override
public long countByCondition(BaseCondition condition) {
    String key = this.getClass().getName();
    return PageCountCacheManager.cacheByTime(key, ()-> mapper.countByCondition(condition));
}
相关推荐
IT古董37 分钟前
第四章:大模型(LLM)】06.langchain原理-(3)LangChain Prompt 用法
java·人工智能·python
东阳马生架构3 小时前
生成订单链路中的技术问题说明文档
后端
轻抚酸~4 小时前
小迪23年-32~40——java简单回顾
java·web安全
程序员码歌5 小时前
【零代码AI编程实战】AI灯塔导航-总结篇
android·前端·后端
Sirius Wu6 小时前
Maven环境如何正确配置
java·maven
java坤坤6 小时前
GoLand 项目从 0 到 1:第八天 ——GORM 命名策略陷阱与 Go 项目启动慢问题攻坚
开发语言·后端·golang
元清加油6 小时前
【Golang】:函数和包
服务器·开发语言·网络·后端·网络协议·golang
健康平安的活着7 小时前
java之 junit4单元测试Mockito的使用
java·开发语言·单元测试
bobz9657 小时前
GPT-4.1 对比 GPT-4o
后端
Java小白程序员7 小时前
Spring Framework :IoC 容器的原理与实践
java·后端·spring