订单数据越来越多,如何优化数据库性能?

news2024/12/23 9:44:24

“增删改查”都是查找问题,因为你都得先找到数据才能对数据做操作。那存储系统性能问题,其实就是查找快慢问题。

存储系统一次查询所耗时间取决两个因素:

  • 查找的时间复杂度
  • 数据总量

查找的时间复杂度取决于:

  • 查找算法
  • 存储数据的数据结构

大多业务系统用现成数据库,数据的存储结构和查找算法都由数据库实现,业务系统基本没法去改变。如MySQL InnoDB存储结构B+树,查找算法大多是树的查找,查找时间复杂度就是O(log n),唯一能改变的,就是数据总量。

所以,解决海量数据导致存储系统慢,就是“拆”,即分片。拆开之后,每个分片里的数据就没那么多了,然后让查找尽量落在某个分片提升查找性能。

存档历史订单数据提升查询性能

数据都具备时间属性,并随系统运行,累计增长越来越多,数据量达到一定程度就越来越慢,如订单数据,一般保存在MySQL订单表,当单表订单数据太多,影响性能,首选方案:归档历史订单。

归档是一种拆分数据策略,把大量历史订单移到另外一张历史订单表。像订单这类有时间属性的数据,都存在热尾效应。大多数访问最近数据,但订单表里大量数据都不怎么常用的老数据。

新数据只占数据总量中很少一部分,所以把新老数据分开,新数据的数据量就少,查询速度快很多。老数据和之前比起来没少,查询速度提升不明显,但老数据很少会被访问,所以慢点问题不大。

这样拆分好处,拆分订单时,要改动的代码少。大部分对订单表的操作都是在订单完成前,这些业务逻辑完全不用修改。即使退货退款这类订单完成后操作,也有时限,这些业务逻辑也不需要修改,原来该怎么操作订单表还怎么操作。

基本只有查询统计类功能,会查历史订单,要稍微调整,按时间,选择去订单表or历史订单表查询。查“三个月前订单”的选项,其实就是查订单历史表。

归档历史订单的流程:

img

  1. 首先我们需要创建一个和订单表结构一模一样的历史订单表;
  2. 然后,把订单表中的历史订单数据分批查出来,插入到历史订单表中去。这个过程你怎么实现都可以,用存储过程、写个脚本或者写个导数据的小程序都行,用你最熟悉的方法就行。如果你的数据库已经做了主从分离,那最好是去从库查询订单,再写到主库的历史订单表中去,这样对主库的压力会小一点儿。
  3. 现在,订单表和历史订单表都有历史订单数据,先不要着急去删除订单表中的数据,你应该测试和上线支持历史订单表的新版本代码。因为两个表都有历史订单,所以现在这个数据库可以支持新旧两个版本的代码,如果新版本的代码有Bug,你还可以立刻回滚到旧版本,不至于影响线上业务。
  4. 等新版本代码上线并验证无误之后,就可以删除订单表中的历史订单数据了。
  5. 最后,还需要上线一个迁移数据的程序或者脚本,定期把过期的订单从订单表搬到历史订单表中去。

类似于订单商品表这类订单的相关的子表,也是需要按照同样的方式归档到各自的历史表中,由于它们都是用订单ID作为外键来关联到订单主表的,随着订单主表中的订单一起归档就可以了。

这个过程中,我们要注意的问题是,要做到对线上业务的影响尽量的小。迁移这么大量的数据,或多或少都会影响数据库的性能,你应该尽量放在闲时去迁移,迁移之前一定做好备份,这样如果不小心误操作了,也能用备份来恢复。

批量删除大量数据

如何从订单表删除已迁走的历史订单数据?直接执行一个删除历史订单的SQL:

delete from orders
where timestamp < SUBDATE(CURDATE(),INTERVAL 3 month);

大概提示删除失败,因为要删除的数据量太大,所以要分批删除。如每批删除1000条记录:

delete from orders
where timestamp < SUBDATE(CURDATE(),INTERVAL 3 month)
order by id limit 1000;

执行删除语句的时候,最好在每次删除之间停顿一会儿,避免给数据库造成太大压力。反复执行这个SQL,直到全部历史订单都被删除。

SQL还能优化,它每执行一次,都要先去timestamp对应索引找出符合条件记录,再把这些记录按照订单ID排序,之后删除前1000条记录。

没必要每次按timestamp比较订单,可先通过一次查询,找到符合条件的历史订单中最大的订单ID,然后在删除语句中把删除的条件转换成按主键删除。

select max(id) from orders
where timestamp < SUBDATE(CURDATE(),INTERVAL 3 month);


delete from orders
where id <= ?
order by id limit 1000;

这样每次删除的时候,由于条件变成主键比较,B+树本身有序,所以查找快,也不需要再额外排序操作。这样做前提条件:订单ID必须和订单时间正相关,大多数订单ID的生成规则都满足这条件,问题不大。

为什么在删除语句中,非得加个排序?按ID排序后,每批删除的记录,基本都是ID连续一批记录,由于B+树有序性,这些ID相近记录,在磁盘物理文件也放在一起,删除效率较高,便于MySQL回收页。

大量的历史订单数据删除完成后,若检查MySQL占用磁盘空间,会发现它占用磁盘空间并没有变小,why?和InnoDB物理存储结构有关。

虽逻辑上每个表是颗B+树,但物理上每条记录都存放在磁盘文件,这些记录通过一些位置指针组织成一颗B+树。当MySQL删除一条记录,只能是找到记录所在的文件中位置,然后把文件的这块区域标记为空闲,然后再修改B+树中相关的一些指针,完成删除。其实那条被删除的记录还是躺在那个文件的那个位置,不会释放磁盘空间。

这么做也是没有办法的办法,因为文件就是一段连续二进制字节,类似数组,不支持从文件中间删除一部分数据。若非要这么删除,只是把这位置后的所有数据往前挪,这等于要移动大量数据,慢。所以,删除只能是标记一下,并不真正删除,后续写新数据时再重用这块空间。

不仅MySQL,很多其他的数据库都会有类似的问题。这个问题也没什么特别好的办法解决,磁盘空间足够的话,就这样吧,至少数据删了,查询速度也快,基本达到目的。

若DB的磁盘空间紧张,非要把这部分磁盘空间释放,可执行OPTIMIZE TABLE释放存储空间。对InnoDB执行OPTIMIZE TABLE就是把这个表重建,执行过程中会一直锁表,即这时下单都会被卡住。这么优化的前提条件:MySQL配置须是每个表独立一个表空间(innodb_file_per_table = ON),如果所有表都是放在一起的,执行OPTIMIZE TABLE也不会释放磁盘空间。

重建表的过程,索引也会重建,这样表数据和索引数据都更紧凑,不仅占用磁盘空间更小,查询效率也会有提升。对频繁插入删除大量数据的表,如能接受锁表,定期执行OPTIMIZE TABLE非常有必要。

如系统接受暂时停服,最快的:

  • 直接新建一个临时订单表
  • 把当前订单复制到临时订单表
  • 再把旧的订单表改名
  • 最后把临时订单表的表名改成正式订单表

这相当于手工把订单表重建,但不需要漫长删除历史订单过程。

-- 新建一个临时订单表
create table orders_temp like orders;


-- 把当前订单复制到临时订单表中
insert into orders_temp
  select * from orders
  where timestamp >= SUBDATE(CURDATE(),INTERVAL 3 month);


-- 修改替换表名
rename table orders to orders_to_be_droppd, orders_temp to orders;


-- 删除旧表
drop table orders_to_be_dropp

总结

对于订单这类具有时间属性的数据,会随时间累积,数据量越来越多,为了提升查询性能需要对数据进行拆分,首选的拆分方法是把旧数据归档到历史表中去。这种拆分方法能起到很好的效果,更重要的是对系统的改动小,升级成本低。

在迁移历史数据过程中,如果可以停服,最快的方式是重建一张新的订单表,然后把三个月内的订单数据复制到新订单表中,再通过修改表名让新的订单表生效。如果只能在线迁移,那需要分批迭代删除历史订单数据,删除的时候注意控制删除节奏,避免给线上数据库造成太大压力。线上数据操作非常危险,在操作之前一定要做好数据备份。

FAQ

这种“归档历史订单”的数据拆分方法,和直接进行分库分表相比,比如说按照订单创建时间,自动拆分成每个月一张表,两种方法各有什么优点和缺点?

复制状态机除了用于数据库的备份和复制以外,在计算机技术领域,还有哪些地方也用到了复制状态机?

复制状态机的应用是非常广泛的,比如说现在很火的区块链技术,也是借鉴了复制状态机理论,它的链,或者说是账本就是操作日志,每个人的钱包,就是状态。它只要保证账本一旦记录后就不会被篡改,那在任何人的电脑上,计算出来的钱包就都是一样的。

“归档历史订单”可以灵活控制,比如把不再会进行修改的订单,迁移到偏重查询快系统(各种NOSQL),不再需要online查询的数据,可以迁移到offline的库中。
直接进行分库分表,会遇到冷热不均的问题,如:电商大促或年节购物旺季订单量与谈季和平季订单可能会量级差别。用时间这个维度去分库分表,操作上相对简单,但是到达这种需要分库分表量级的系统,切分的灵活性更加重要,怎么分业务场景不同切分维度也会不同。

如果不进行OPTIMIZE,想通过历史表来提升性能的目的岂不是达不到了?

不执行OPTIMIZE也是可以提升性能的。数据和索引虽然在物理上没有删除,但逻辑上已经删除掉了,执行查询操作的时候,并不会去访问这些已经删除的数据。

比如,原来有100条数据,删除完成后剩了10条。虽然100条数据都在磁盘文件中,但这时候执行一次全表扫描,MySQL只会访问剩下的10条数据。

一下子和Java的GC算法产生了共鸣。
创建新表的方式,只复制少部分数据,效率更高,但你要能接受这段时间的STW。这是复制算法。
历史归档,删除数据的方式,会产生碎片,利用率低。只有到空间不足的情况下,才进行压缩整理(OPTIMIZE)。这是标记清理算法,关键时刻再整理(STW),CMS GC就是这个思路。

是的,磁盘碎片和内存碎片产生的原因是一样的,所以清理的思路很多都是相似的。

按时间分库分表一直有个疑惑, 按月进行分表, 有几个月数据很小,有几个月数据特别大,这种会怎么处理

这种情况可能就不适合按月来分片。

alter table A engine=InnoDB 命令来重建这样也能达到释放空间的效果吧?

是可以的。

定期归历史的方式其实oracle的使用频率是最高的。
不过删主键的方式确实不曾想到和尝试过,觉得短期不失为不错的选择。
自动分表总归会有坑:这也是为何自动化的极限还是要人去监控;自动化减少的人的机械操作而已,不是不需要人去操作和监控,尤其数据库数量众多时还是要人去自动化。
手工虽麻烦细节上的把控会比较好:细节会把控的比较好。
相对合理的方式应当是二者结合:1)拆分之前人为的做一次检查,2)拆分的动作自动化去执行,3)结果由人去复核。
毕竟当需要同时拆分的工作量很庞大时不可能全部都是手工操作,这其实就像运维:一个人去操作2-3台可能,20-50其实就很困难了,500以上要全部人为操作基本就只能自动化+人为操作了。

\1. 自动分表需要事先做好预估,把时间间隔设置好,如果表数据增长速度不均匀(例如淡季旺季,后期业务膨胀),可能需要重新设计分表规则,很麻烦。表名也变化了,代码侵入性比较大。
优点就是如果数据增长速度变化不大,不用持续做归档。

  • \2. 归档的好处是代码侵入性低,因为热表名字还是一样。表增长速度变成也能灵活改变归档数据大小跟速度。
    缺点就是需要持续归档迁移,后期归档数据太大也会遇到瓶颈

    什么情况下需要归档,什么情况需要分库分表呢?有什么具体的指标吗?

    如果归档能解决问题,就不要分库分表。

    归档历史数据的优点是简单,对系统的改造少,缺点是不是长久之计
    分库分表需要对数据访问层做架构变更,对系统的改造大,要考虑数据分布,对接口查询性能等业务需求的影响,另外我觉得按时间分表跟我们设计这个分库分表就不符,我们做业务数据分库分表就是想数据打散,按时间分达不到这个目的,按时间适用在做一体化系统,因为这些系统有很多报表统计需求可能用的上

    任何时间属性相关的数据基本都可以这样处理(比如聊天数据),这种处理办法和分库分表等并不冲突。这种办法相对简单大部分情况下效果也比较好,只是如果业务发展很好,那么订单表的数据依旧有可能很多,另外历史数据表依旧是需要查询的,时间越久数据量越多,查询历史数据太慢的话迟早也会是个问题。
    分库分表这种方案需要选比较好的shard key,在数据统计上会麻烦一些,单表数据量上来之后依旧有归档的需求。
    按月新建表会有数据热点问题,查询和统计还是会比较麻烦。

    比这个删除规律 是不是有问题啊?
    1、创建temp表
    2、把历史数据迁移老表
    3、check历史数据条数
    4、删除老数据?

    在rename 之前 插入之后 这时候有数据进来 数据会丢在老表里面了?

    所以我们说,这个方案的前提是必须得停机操作。

    1.前者操作所有数据都会有一个路由的前置操作,这是有开销的。其次,因为分表了,所以其区域查找这种就需要多表数据做聚合,这就会让查询变得很复杂低效(采用mycat这种中间件可以规避业务代码大量改动的问题,但聚合数据的开销依旧是跑不掉的,而且引入中间件还有多一条的问题)。

    2.后者其实性能,操作成本,对业务代码的侵入程度都比前者有优势,唯一遗憾的就是其应用场景有限。只有在整体业务单量不大,且归档数据操作概率极低的情况下适用。因为如果业务单量很大,比如日单量一百万,那么这个热数据表能存多久的数据?十天?二十天?这样的时间区间是严重不符合归档条件的,往往归档数据都是半年,少说三四个月的完结订单。而且归档数据,数据量很大。意味着性能很差,频繁导入导出,查询修改,是支撑不住的。

    最近的订单表往归档表挪数据的过程中可能一份数据在两张表都存在 这个时候用户查询全部订单的时候是否我们在应用利用是用去重去剔除重复数据

    如果要同时查二个表,那合并和去重就在所难免。一般情况下,最好能设计好业务逻辑,尽量不要同时查当前和历史表。

    归档历史数据一般可以根据日前时间分类新建表
    删除历史数据要注意分批删除,还有就是删除数据但是磁盘空间并没有释放,可以执行optimism table 进行磁盘空间释放执行过程会锁表
    还有一种方案就新建一张表迁移所需数据到新的表
    注意别忘记和要删除的表的其他表的相关数据

    保证关系数据库数据最小化,在抗流量的过程中很有作用。历史数据异步同步到大数据环境,定期删除关系数据库里的归档数据。保证sql 执行效率

    把历史订单数据归档方案实现相对简单,也很有效果,需要注意的就是归档时注意对线上服务的影响。

    如果采用按照订单创建时间分库分表,优点是省去了后面归档历史数据的重复工作,在一定程序上可以提高写入和查询性能,但也有不足。

    首先就是采用分库分表在技术复杂度上相比历史数据归档还是高一些的

    其次就是如果刚好到新的一个月,前一个月的数据还是属于热数据,所以会涉及多表查询

    最后就是这种方案会造成产生大量的表,如果订单数据不大,每个表中数据量也不会太大,有点浪费资源了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/147452.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Java-SpringBoot+vue实现的前后端分离信息管理系统设计和实现

基于Java-SpringBootvue实现的前后端分离信息管理系统设计和实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言…

后台执行限制总结

后台限制的发展历程 前台定义 针对后台Service procState < PROCESS_STATE_IMPORTANT_BACKGROUND7 针对后台启动Activity procState < PROCESS_STATE_BOUND_TOP3 针对后台启动FGS/后台启动FGS的while-in-use权限 procState < PROCESS_STATE_BOUND_FOREGROUND_SERVICE…

【Linux】文本编辑器-vim使用

目  录1 vim的基本概念2 vim的基本操作3 vim常用模式命令集3.1 vim正常模式命令集3.2 vim末行模式命令集4 vim的简单配置1 vim的基本概念 vim编辑器与vi编辑器一样都是多模式编辑器&#xff0c;不同的是vim编辑器是vi编辑器的升级版本&#xff0c;vim不仅兼容vi的所有指令&am…

Django by Example·第二章|Enhancing Your Blog with Advanced Features@笔记

Django by Example第二章|Enhancing Your Blog with Advanced Features笔记 这本书的结构确实很不错&#xff0c;如果能够坚持看下去&#xff0c;那么Django框架的各种用法也就掌握的七七八八了。之前写过一篇这本书的第一章&#xff0c;看完第一章就算是入门了&#xff0c;但…

acwing差分

题目&#xff1a;输入一个长度为 n 的整数序列。接下来输入 m 个操作&#xff0c;每个操作包含三个整数 l,r,c&#xff0c;表示将序列中 [l,r] 之间的每个数加上 c。请你输出进行完所有操作后的序列。输入格式第一行包含两个整数 n 和 m。第二行包含 n 个整数&#xff0c;表示整…

【C++高阶数据结构】跳表(skiplist)

&#x1f3c6;个人主页&#xff1a;企鹅不叫的博客 ​ &#x1f308;专栏 C语言初阶和进阶C项目Leetcode刷题初阶数据结构与算法C初阶和进阶《深入理解计算机操作系统》《高质量C/C编程》Linux ⭐️ 博主码云gitee链接&#xff1a;代码仓库地址 ⚡若有帮助可以【关注点赞收藏】…

第十一章Thymeleaf学习

文章目录什么是Thymeleaf什么是模板引擎Thymeleaf的同行Thymeleaf优势一个实例来认识大概过程导入对应的jar包配置对应的xml文件对应的ViewBaseServlet编写——对应的模板引擎写对应的Servlet类并且继承ViewBaseServlet对应index.html资源——对应的模板Thymeleaf的基础语法th名…

337. 打家劫舍 III

目录题目思路代码题目 小偷又发现了一个新的可行窃的地区。这个地区只有一个入口&#xff0c;我们称之为 root 。 除了 root 之外&#xff0c;每栋房子有且只有一个“父“房子与之相连。一番侦察之后&#xff0c;聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”…

笔试强训(11)

第一题:二进制插入二进制插入__牛客网 给定32位整数n和m&#xff0c;同时我们指定i和j&#xff0c;将m的二进制位数插入到n的二进制位数的j到i位&#xff0c;我们保证n的j到i位均是等于0的&#xff0c;况且m的二进制位数小于等于i-j1&#xff0c;其中二进制的位数从0开始从低到…

js设计模式(八)-总体感受一下设计模式

前言 首先&#xff0c;不得不说我们是站在巨人的肩膀上写代码&#xff0c;前辈们已经很合理的帮助我们总结出来了23种设计模式&#xff0c;虽然有些已经被语言直接使用Api实现了&#xff0c;感谢走在前沿的攻城狮。 但是真真正正的看一遍所有的设计模式还是很有必要的&#x…

MyBatis查询数据库

1.MyBatis 是什么&#xff1f; MyBatis 是⼀款优秀的持久层框架&#xff0c;它⽀持⾃定义 SQL、存储过程以及⾼级映射。MyBatis 去除了⼏乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接⼝和 Java POJO&#xf…

计算机基础——计算机分类

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 前言 本章将会讲解计算机分类应用领域以及发展趋势 一.计算机分类 计算机并非只有日常所…

并行计算 Clion配置使用OpenMP

文章目录配置CMakeList.txt文件OpenMP之HelloWorld数据共享属性shared子句private子句default子句default(shared)default(none)配置CMakeList.txt文件 文件底部加入以下内容&#xff0c;即可支持OpenMP FIND_PACKAGE(OpenMP REQUIRED) if (OPENMP_FOUND)message("OPENM…

STM32MP157驱动开发——Linux DAC驱动

STM32MP157驱动开发——Linux DAC驱动0.前言一、DAC 简介二、驱动源码分析1.设备树下的 DAC 节点2.驱动源码分析1&#xff09;stm32_dac 结构体2&#xff09;stm32_adc_probe 函数3&#xff09;stm32_dac_iio_info 结构体三、驱动开发1.修改设备树2.使能DAC驱动四、 运行测试0.…

读书笔记 -公司改造 和 紧迫感

读书笔记 -公司改造 - 三枝匡 读书笔记 -公司改造 - 三枝匡 2022 年夏天的时候在微信读书上读了这本书&#xff0c;这是我们 CSDN 的创始人蒋涛推荐的&#xff0c;当时记了一些笔记如下。 总结&#xff1a; 每个有一定的历史&#xff0c;比较成功、或者尚未非常成功的公司遇…

基于Java+SpringBoot+vue+element实现毕业就业招聘系统

基于JavaSpringBootvueelement实现毕业就业招聘系统 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码…

用最简单的案例带你掌握C++中各种指针

1、前言 指针&#xff0c;作为C/C中最神秘、功能最强大的语法&#xff0c;着实是难以理解 、难以掌握、难以运用。&#x1f625; 但是&#xff0c;能灵活的使用指针&#xff0c;对利用C/C开发程序将有很大的帮助&#xff0c;让我们一起来了解了解吧。 2、啥是指针&#xff1f…

参加《2022 中国开发者影响力盛典》我的 4 重收获!

感谢 CSDN 邀请&#xff0c;西红柿有幸参加了 2022 中国开发者影响力盛典暨 CSDN 企业生态汇&#xff0c;让我有了一个不虚此行的下午&#xff0c;也跟大家分享一下我在会上的 4 重收获吧~第一重收获&#xff1a;互联网圈大佬 会议聚焦开发者生态建设主题&#xff0c;分享了 CS…

分布式基础篇4 —— 基础篇完结

分类维护一、三级分类后端实现准备工作跨域问题关闭 ESLint 检查前端实现二、分类删除前端完善分类列表后端实现——删除配置发送请求代码片段前端实现——删除三、分类增加前端实现四、分类修改五、拖拽菜单拖拽效果实现拖拽数据收集拖拽功能完成拖拽功能完善六、批量删除品牌…

JS知识补充-JS原型链

概述JS原型链别名&#xff1a;隐式原型链作用&#xff1a;根据一定路径查找属性&#xff08;方法&#xff09;作用举例&#xff1a;我们定义一个构造函数Fn&#xff0c;使用此构造函数创建一个对象fn1&#xff0c;接着使用创建的对象fn1去调用toString方法并打印&#xff0c;我…