为了解决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));
}
相关推荐
_oP_i9 分钟前
Pinpoint 是一个开源的分布式追踪系统
java·分布式·开源
mmsx12 分钟前
android sqlite 数据库简单封装示例(java)
android·java·数据库
武子康37 分钟前
大数据-258 离线数仓 - Griffin架构 配置安装 Livy 架构设计 解压配置 Hadoop Hive
java·大数据·数据仓库·hive·hadoop·架构
豪宇刘2 小时前
MyBatis的面试题以及详细解答二
java·servlet·tomcat
秋恬意2 小时前
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别
java·数据库·mybatis
刘大辉在路上2 小时前
突发!!!GitLab停止为中国大陆、港澳地区提供服务,60天内需迁移账号否则将被删除
git·后端·gitlab·版本管理·源代码管理
FF在路上3 小时前
Knife4j调试实体类传参扁平化模式修改:default-flat-param-object: true
java·开发语言
真的很上进3 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
众拾达人3 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
皓木.3 小时前
Mybatis-Plus
java·开发语言