当前位置: 首页 > news >正文

数据可视化的中间表方案

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 {@Autowiredprivate LogSummaryMapper logSummaryMapper;@Autowiredprivate 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代码,指导评论。

http://www.dtcms.com/a/393754.html

相关文章:

  • 编译运行duckdb rust插件模板extension-template-rs
  • 接口测试流程+jmeter并发+面试题(总结)
  • JMeter下载安装及入门教程
  • Oracle体系结构-Java Pool详解
  • ​​Service Worker 缓存 与 HTTP 缓存 是什么关系?
  • c++ 之三/五法则
  • 传输层协议 UDP
  • 关于类和对象(一)
  • 多人协作下的游戏程序架构 —— 分层方案
  • 机器学习中三个是基础的指标:​准确率 (Accuracy)​、精确率 (Precision)​​ 和 ​召回率 (Recall)​
  • 《Web端图像剪辑方案:Canvas API与JavaScript实现》
  • DeepSeek 登《自然》封面,OpenAI 推出 GPT-5-Codex,Notion Agent 首亮相!| AI Weekly 9.15-9.21
  • 多线程-初阶
  • 在 R 语言中,%>% 是 管道操作符 (Pipe Operator),它来自 magrittr 包(后被 dplyr 等 tidyverse 包广泛采用)
  • IMX6ULL学习笔记_Boot和裸机篇(1)--- SEGGER Embedded Studio 和 Uboot 环境搭建
  • 纯JS代码录制网页中的视频(可多线操作)
  • Javase 基础加强 —— 11 线程池
  • 分布式锁-Redis实现
  • 对于ModelScope的AI模型git部署感悟
  • [论文阅读] 人工智能 + 软件工程 | 从“人工扒日志”到“AI自动诊断”:LogCoT框架的3大核心创新
  • 【软考中级 - 软件设计师 - 应用技术】软件工程案例分析之软件测试实践
  • AI:读《老人与海》有感
  • 定制开发开源AI智能名片S2B2C商城小程序:产业互联网时代的创新商业模式
  • .env与.gitignore:现代软件开发中的环境管理与版本控制防护
  • 理解重参数化
  • css 给文本添加任务图片背景
  • CSS中的选择器、引入方式和样式属性
  • CSS 入门与常用属性详解
  • Linux 下 PostgreSQL 安装与常用操作指南
  • 【Linux】CentOS7网络服务配置