数据可视化的中间表方案

1、数据统计

数据统计是所有软件应用的必备模块,因为需要通过数据去了解整个项目的运营情况,并且随着大屏的兴起变的越来越重要。先看一个实例:网站访问数据统计

统计类的SQL本身其实不难,大家都知道无外乎就是联表 ,以及使用SUM、Count等聚合函数。

然而既然是数据统计,往往就伴随着海量的数据。这时联表、聚合函数等操作就是致命的操作,动辄几十秒的慢查询,很容易拖垮生产应用,当然也可能拖垮你的职业生涯~

大家很容易想到,慢SQL肯定是索引没用好。然而现实是在千万级别的数据面前一切的索引优化、命中分析都是枉然~

2、心路历程

去年下半年,公司临时接到一个商品销售类项目。工时很紧,技术部全员投入,首版要求2周内上线。

我之前做的前端开发,现在已不在技术部任职,做软件售前/产品类的工作。但技术部实在忙不过来,把数据库账号密码给我,数据大屏这块就交给我开发了。大屏展示中样式和Echarts我都能搞,但SQL我只在大学时学过一点基础,从未在项目中实际开发过。但受命于公司危难之际,我只能现学现卖。

大屏第一版我直接查交易记录表,SQL写的朴实无华,毫无技巧与效率可言!但项目刚上线,每天1万多订单,全量20万数据。虽然Sum、Count、Count (distinct)一顿操作,但一切都还兜得住,大屏还获得甲方的大力赞赏!

然后遭遇国庆节,存量数据来到140万,SQL执行时间已经2秒了。此时开始优化SQL,优化索引,效果不是很明显。

当存量数据来到500万时,所有sql语句和索引层面的优化都做到了极限,查询时间依旧突破4秒。再后来因为我的慢SQL拉爆了数据库服务,导致生产业务中断了一次,后来我这个小透明因此出名了一次,我知道是时候调整方案了。

3、中间表方案

我之前干前端开发的,其实不懂Java也没中间表的概念,还是技术部同事教我中间表的用法。

中间表方案:根据大屏统计维度,设计一张中间表。先处理存量数据,每次只统计一天的数据,将上线后每一天的数据都汇集进中间表。然后,定时任务每天凌晨统计前一天的数据,插入中间表。统计类SQL全都改为从中间表查询,整体方案如下图所示:

整体架构

此方案新增了后线库和中间表,将生产业务与统计类需求隔离开。生产库只处理业务中的增删改查,然后同步给后线库 。统计类SQL全部查询后线库的各种中间表,杜绝此前因统计类SQL影响生产业务的情况。

在凌晨汇总后线库前一天的数据到中间表,数据库资源占用极小,统计类SQL查询效率也是毫秒级别的。这才是能保障生产数据最稳定和安全的方案。

4、绝知此事要躬行

当时我只负责SQL编写与大屏展示,Java开发与中间表的设计、定时任务、数据汇集等都是技术部的同事帮忙干的。今年我自学了Java,写了**个人网站**,在数据统计功能也实践了相关的各个操作环节,非常有趣。具体如下:

我个人网站的数据统计页面

4.1、数据来源

我的网站做了前端埋点,大部分操作都有操作记录,存在log表中。比如点击菜单、翻页、留言、评论等操作,都记录一条数据,将这些步骤汇总起来就是用户的访问轨迹了。

我还创建了一个log_daily_ip_summary表,定时任务每10分钟将log表当天的数据以IP分组汇总后,插入到log_daily_ip_summary表中。然后如上图所有的图表都可以从log_daily_ip_summary表中轻松高效获取。

两个表的字段基本一样,只不过中间表(log_daily_ip_summary)是以IP分组的汇总数据。这种操作很基础,但足够承受当前的访问量,且对统计类SQL的编写提供了极大的便利性。

log表

中间表

4.2、汇集操作

学了Java后发现,以前觉得高端大气的定时任务原来被SpringBoot封装的如此简单易用,方便快捷。使用中间表这种出发方法,其实是把原有直接查业务表SQL的工作量,前置到了中间表的设计及定时任务的编写上了。

如下分享下我蹩脚的定时任务代码,编写一个查询并插入一天数据的方法,然后定时任务和存量数据回填都用这个方法。

复制代码
@Slf4j
@Service
public class LogSummaryJob {
    @Autowired
    private LogSummaryMapper logSummaryMapper;
    @Autowired
    private LogService logService;
    private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    /**
     * 应用启动后自动回填历史数据
     */
    public String backfillHistoryData() {
        // 先清空数据
        logSummaryMapper.cleanAll();

        // 定义需要回填的日期范围
        // 网站从4月1日上线,批量回填数据到当日
        // 配合10分钟定时任务,该任务不停的汇总当日数据,即可覆盖所有时间日志数据
        LocalDate startDate = LocalDate.of(2025, 4, 1);
        LocalDate endDate = LocalDate.now();

        List<LocalDate> datesToProcess = new ArrayList<>();
        LocalDate currentDate = startDate;

        // 填充日期数组
        while (!currentDate.isAfter(endDate)) {
            datesToProcess.add(currentDate);
            currentDate = currentDate.plusDays(1);
        }

        // 并行处理以提高速度(如果数据量大)
        datesToProcess.parallelStream().forEach(this::processDate);

        System.out.println(DateUtils.getCurrentDateTime() + ": 所有数据回填完成!");
        return DateUtils.getCurrentDateTime() + ": 所有数据回填完成!";
    }

    /**
     * 每10分钟汇总log表中的数据到中间表
     */
    @Scheduled(cron = "0 */10 * * * ?") // 每10分钟执行一次(秒 分 时 日 月 周)
    public void periodicSummaryTask() {
        // 每次汇总数据,先清空中间表中当天的汇总数据
        logSummaryMapper.deleteToday();

        LocalDate today = LocalDate.now();
        processDate(today);
        System.out.println("当日日志数据汇总完成,处理日期:" + DateUtils.getCurrentDateTime());
    }

    // 汇总某一条函数
    private void processDate(LocalDate date){
        String dateStr = date.format(dateFormatter);
        String nextDateStr = date.plusDays(1).format(dateFormatter);
        // 获取需要排除IP的SQL字符串(sunq的访问ip要剔除掉)
        String excludeSunqSql = logService.excludeSunqSql(dateStr + " 00:00:00",nextDateStr + " 00:00:00");

        logSummaryMapper.insertDailyIpSummary(dateStr, dateStr + " 00:00:00", nextDateStr + " 00:00:00", excludeSunqSql);
    }

    /**
     * 手动触发某天的数据处理(用于测试或补数据)
     */
    public void manualProcessDate(String dateStr) {
        LocalDate date = LocalDate.parse(dateStr, dateFormatter);
        processDate(date);
    }
}

4.3、源码开源

我的个人网站地址:码语人生,完全开源:Github地址

适合从前端转全栈,或者初学者实践练手,欢迎大家fork代码,指导评论。

相关推荐
码界筑梦坊2 小时前
269-基于Python的58同城租房信息数据可视化系统
python·mysql·信息可视化·数据分析·flask·毕业设计·echarts
马克学长3 小时前
SSM滁州学院考研信息分享论坛0iaj2 (程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·考研·ssm
安得权3 小时前
Ubuntu18.04 MySQL5.7.42 内存升高导致OOM MySQL重启解决办法
mysql
先做个垃圾出来………3 小时前
Pydantic库应用
java·数据库·python
XiaoMu_0013 小时前
MySQL、PostgreSQL、MongoDB和Redis全面对比
mysql·mongodb·postgresql
深耕AI3 小时前
【12/20】数据库高级查询:MongoDB 聚合管道在用户数据分析中的应用,实现报告生成
数据库·mongodb·数据分析
望获linux4 小时前
【Linux基础知识系列:第一百三十四篇】理解Linux的进程调度策略
java·linux·运维·服务器·数据库·mysql
老华带你飞4 小时前
水果购物网站|基于java+vue的水果购物网站系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·水果购物网站系统
MAGICIAN...4 小时前
【mysql】内部技术架构
数据库·mysql·架构