为了解决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));
}
相关推荐
武子康2 分钟前
大数据-96 SparkSQL 语句详解:从 DataFrame 到 SQL 查询与 Hive 集成全解析
大数据·后端·spark
孤廖14 分钟前
【算法磨剑:用 C++ 思考的艺术・Dijkstra 实战】弱化版 vs 标准版模板,洛谷 P3371/P4779 双题精讲
java·开发语言·c++·程序人生·算法·贪心算法·启发式算法
码畜也有梦想27 分钟前
Maven中optional的作用
java·jenkins·maven
云和数据.ChenGuang1 小时前
java常见SSL bug解决方案
java·bug·ssl
songx_991 小时前
leetcode29( 有效的括号)
java·数据结构·算法·leetcode
于樱花森上飞舞1 小时前
【java】常见排序算法详解
java·算法·排序算法
维持好习惯1 小时前
复杂Excel文件导入功能(使用AI快速实现)
java·spring boot·excel
酷炫码神1 小时前
第 2 篇:Java 入门实战(JDK8 版)—— 编写第一个 Java 程序,理解基础运行逻辑
java·开发语言·策略模式
像风一样自由20201 小时前
Go语言详细指南:特点、应用场景与开发工具
开发语言·后端·golang
月阳羊1 小时前
【硬件-笔试面试题-93】硬件/电子工程师,笔试面试题(知识点:波特图)
java·经验分享·单片机·嵌入式硬件·面试