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

“数据中台、读写分离、表分区”解决MySQL 单表数据量、并放量双高的效率瓶颈

需求情景:现有一数据库表,用于记录每一台设备的各种指标项数据,每台设备指标项约150个左右,共有10台设备(后期还会增加),每台设备每2秒写入1次数据,即:数据库单表每秒写入数据量=10台设备*150个指标项数据/2 = 750 写/秒 的并发。单日写数据量高达6,480万。在单台数据库服务器的情况下肯定并发卡死,更不用谈查询效率。

解决方案:

  1. 在数据采集端,将每台设备每次需要采集的数据做成json字符串,而不是每个指标项都做一次插入,如下图所示:

这是一个很重要的改动,网上有很多说法,类似“数据中台”之类的,就是专门有个地方以串行方式接收数据,保证数据不掉。这里用的不是数据中台的全部,只是用了其部分思想。如此,每台设备每次传输的数据,不管有多少,在程序采集端就构建为一条json字符串。10台设备现在每2秒传一次的写并发现在为:10/2 = 5写/秒,相较于750写/秒的并发,大大减少。单日写入数据为:43.2万,记录从数量上看减少150倍。

  1. 库表结构设计

为保障写表效率,尽量不做索引。数据库表结构如下:

表结构说明:

is_split的作用是用于程序级实现数据的转移,后面做读表的时候有用。

这里,为了保证写效率,只对主键建立了索引,其实,这个索引不建立都没有什么问题。

  1. 读、写分离设计

此处的读写分离不是真正意义上的读写分离,没有用到rose等专用软件来做。就是通过定时事物进行数据的批量写完成。

如上图所示,device_data数据定时写入device_data_read表中。存储过程如下所示:

CREATE DEFINER=`root`@`%` PROCEDURE `DW_division`()
begin
declare temp_latest_id bigint(50);

select id into temp_latest_id from device_data order by id desc limit 1;-- 通过雪花id找到所要处理的数据的锚点。

insert into device_data_read(id,json_str,record_time,is_split,iot_id) 
select id,json_str,record_time,1,json_extract(json_str,'$.iotid') from device_data where iot_id is null and id<=temp_latest_id;


update device_data set is_split=1, iot_id=json_extract(json_str,'$.iotid') where iot_id is null and id<=temp_latest_id;
end

之前用雪花id的作用在此处就体现出来了,可以通过有序的数字类型,快速锚定数据点。这里做了两步操作:

  • 将数据批量写入读表,通过insert into ... select ...方式批量写入数据库表效率很高;
  • 如果采集程序是自己写的,后面的json_extract都可以不用,可以进一步提高程序效率,由于采集程序是第三方完成,所以这里的效率还是受到一定影响;
  • 做完后,将is_split标记设置为1,避免停电、宕机后不知道程序做到哪了的情况。

定时调用存储过程,由于对查询的效率实施性要求并不是那么高,此处的数据传输方案是10秒1次,建立一个定时任务即可。如下图:

  1. 读表的处理(索引+动态表分区)

读表在结构上与写表完全一致,但在索引建立上,可以根据自身查询需要建立多个索引字段。

如上图所示,读表增加了1个索引。

考虑到单表单日数据还是有43.2万,在超过千万数据的情况下,查询无论如何都会非常慢。此时,通过引入表分区和动态表分区(通过定时任务,每天调用一次表分区函数)思想,将每日数据做成一个表分区,即可较好地解决实时查询问题。如下图:

建立动态表分区时有几个需要注意的地方:

  • 用于分割表分区的字段需要是整形,或者能够通过函数转化为整形的字段,如上图,本人通过日期进行分割,那么:就需要通过to_days函数将当前日期转为整形。
  • 通过navicat进行表分区设计,只能实现静态的表分区,想要每天做一个分区,需要根据自身业务需求编写动态增加表分区的代码,其实:核心代码只有一条:
alter table device_data_read add partition(partition 分区名 values less than (to_days('分区值')))

基于上述代码,构建存储过程如下:

CREATE DEFINER=`root`@`%` PROCEDURE `auto_table_division`()
begin
declare tempTimeStr varchar(40);
declare parName varchar(40);
declare sqlstr varchar(2000);
set tempTimeStr = date_format(NOW(),'%Y%m%d');
set parName = CONCAT("p",tempTimeStr);
set sqlstr = "alter table device_data_read add partition(partition 分区名 values less than (to_days('分区值')))";
set sqlstr = replace(sqlstr,'分区名',parName);
set sqlstr = replace(sqlstr,'分区值',now());


set @sqlstrPara = sqlstr;
select @sqlstrPara;
prepare sqlsentence from @sqlstrPara;
execute sqlsentence;
DEALLOCATE prepare sqlsentence;
end

业务逻辑比较简单,代码不详述。

  • 建立时间,每天凌成0点0分调用1次即可完成动态表分区的建立工作。
  1. 经过上述调整,性能有非常明显的改善,业务逻辑存储过程在40万左右记录下,执行效率可保证在1秒以内:

SCADA实现了实施高效展示(最大延时10秒)

相关文章:

  • 计算机找不到vcruntime140_1.dll,无法继续执行代码快速解决方法
  • 计算机网络组成—物理层
  • ACE框架学习3
  • 微信小程序详解
  • Jenkins构建触发器-Git hook自动触发构建
  • MATLAB初学者入门(23)—— 旅行商问题(TSP)优化
  • 【JS】【Vue3】【React】获取滚轮位置的方法:JavaScript、Vue 3和React示例
  • 数据库 -neo4j的基本操作
  • Mac OS 搭建C++开发环境【已解决】
  • ETH网络中的账户
  • Go语言反射(reflect)
  • [Mac软件]Adobe Substance 3D Stager 2.1.4 3D场景搭建工具
  • 调用百度云语音转文本
  • SDWAN的功能和场景应用
  • JS 第三课(JS的变量类型和数据类型)
  • Matlab论文插图绘制模板第56期—曲面图(Surf)
  • 全志 Tina Linux 存储介质切换:eMMC,SPI NAND,SPI NOR,SD Card,SD NAND
  • FPGA—从加法运算理解流水线的作用
  • C++从入门到精通 C++98.11.14.17
  • 基于单片机的16×16点阵的滚动显示屏设计
  • LeetCode 940. 不同的子序列 II
  • 【VUE基础】webpack
  • 【漏洞复现-discuz-wooyun-命令执行】vulfocus/discuz-wooyun_2010_080723
  • SDWAN和MPLS谁才是最佳选择
  • 记一次失败的使用python selenium刷课学习通脚本(细节满满)+关于使用selenium的疑难杂症解决+json数据请求的疑难杂症+py冷门知识
  • Mybatis架构,SqlSessionFactory源码分析
  • 我终于读懂了设计模式的七大原则。。。
  • stm32f4xx-SPI
  • 高数(下) 第十二章:无穷级数
  • LeetCode·每日一题·940.不同的子序列 || · 动态规划
  • 【云原生】Elasticsearch + kibana on k8s 讲解与实战操作
  • 神经网络过拟合什么意思,神经网络中解决过拟合