5000w+ 的大表如何拆?亿级别大表拆分实战复盘

👉 这是一个或许对你有用的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料: 

7304e3ae4c508960d31dd042700ef5da.gif

👉这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号等等功能:

  • Boot 仓库:https://gitee.com/zhijiantianya/ruoyi-vue-pro

  • Cloud 仓库:https://gitee.com/zhijiantianya/yudao-cloud

  • 视频教程:https://doc.iocoder.cn

【国内首批】支持 JDK 21 + SpringBoot 3.2.2、JDK 8 + Spring Boot 2.7.18 双版本 

来源:juejin.cn/post/
7078228053700116493

e24e869d909b76fa4d7e8ddf664e6d86.jpeg


前言

笔者是在两年前接手公司的财务系统的开发和维护工作。在系统移交的初期,笔者和团队就发现,系统内有一张5000W+的大表。

跟踪代码发现,该表是用于存储资金流水的表格,关联着众多功能点,同时也有众多的下游系统在使用这张表的数据。

进一步的观察发现,这张表还在以每月600W+的数据持续增长,也就是说,不超过半年,这张表会增长到1个亿!

fc32acc4776549eaf0e11e5525a46ace.jpeg
笔者内心:(麻了)

这个数据量,对于mysql数据库来说是绝对无法继续维护的了,因此在接手系统两个月后,我们便开起了大表拆分的专项工作。(两个月时间实际上主要用来熟悉系统、消化堆积需求了)

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

拆表前系统状态

  • 涉及到流水表流水的接口超时频发,部分接口基本不可用

  • 每日新增流水缓慢,主要是插入数据库的时候非常慢

  • 单表占用空间过大,DBA的数据库监控经常报警

  • 无法对表进行变更,任何alter操作都会引起主从的高延迟和长时间锁表

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud

  • 视频教程:https://doc.iocoder.cn/video/

拆表的目标

  • 将流水大表数据拆分至各个分表,保证每张分表数据在1000W左右(经验上看单表1000W的量对mysql来说没啥压力)

  • 在拆表的前提下,针对不同接口的查询条件进行优化,保证各个对外、对内接口的可用性。彻底杀死mysql慢查询。

难点分析

  • 该表的数据可以说是整个财务系统最基础的数据,相关功能和下游系统非常多。这要求开发、测试和上线流程必须极其严密,任何小失误都会引起大问题。

  • 涉及的场景非常多。统计下来,一共有26个场景,需要改造32个mapper方法,具体需要改造的方法就更加无计其数了。

  • 数据量非常大,迁移数据的过程必须保证系统稳定。

  • 用户较多且功能重要。分表功能上线时,必须尽量压缩系统无法使用时长,同时需要保证系统可用性。这要求团队必须设计完整可靠的上线流程、数据迁移方案、回滚方案、降级策略。

  • 上文提到,表的拆分势必带来部分接口的变化,接口的变化又会带来其他系统的改造。如何推动其他系统进行改造,如何协调多方合作的开发、测试和上线是另一个难点。

整体过程

45d27ceab51747c8d4c20cc361f36b4d.jpeg

具体细节

分表中间件调研

分表插件:采用sharding-jdbc作为分表插件。

其优势如下:

1、支持多种分片策略,自动识别=或in判断具体在哪张分表里。

2、轻量级,作为maven依赖引入即可,对业务的侵入性极低。

为提升查询速度,在整个项目的初期,团队成员考虑引入ES存储流水以提升查询速度。

经过与ES维护团队的两轮讨论,发现公司提供的ES服务对于我们的业务场景并不匹配(见表),经过反复考量,最终我们放弃了引入ES的计划,直接从数据库查询数据,采用每张表设置一个查询线程的方式提升查询效率。

2fd494e248d87ab8fe85aec3a60a9d86.png
分表依据的选择

分表的方式有很多种,有纵向分表,有横向分表,有分为固定的几个表存储然后取模进行表拆分等等。总的来说,适合我们具体业务的分表方式只有横向分表。

因为对于资金流水这种特殊数据来说,是不能清理数据的,那么纵向分表和拆成固定的几个表都不能解决单表数据无限膨胀的问题。而横向分表,可以把每张表的数据量恒定,到一定时间后可以进行财务数据归档。

分表的依据一般都是根据表的某个或者某几个字段进行拆分,最终其实是对数据和业务分析综合出来的结果。总的来说,原则有这几个:

  • 尽可能选择查询条件里最常出现的字段,这样能够减少方法改造的工程

  • 需要考虑根据某个字段拆分数据是否能够均匀分布,是否能够满足单表1000W左右的要求

  • 该字段必须是必现字段,不允许出现空值

综合分析我们的数据以及业务需要,“交易时间”这个分表依据就呼之欲出了。

首先,这个字段作为流水最重要的字段之一一定会出现;

第二,如果按照交易月份进行拆表,每张表大概也就是600W-700W的数据;

最后,有70%的查询都附带“交易时间”作为查询条件。

技术难点

多数据源事务问题

sharding-jdbc在使用的时候是需要用自己的独立数据源的,那么就难免出现多数据源事务问题。

这个我们通过自定义注解,自定义切面开启事务,通过方法栈逐层回滚or提交的方式解决的。出于保密原则,具体代码细节不再展开。

多表的分页问题

拆表一定会引起分页查询的难度增加。由于各个表查出来的数据量不等,原始的sql语句limit不再适用,需要设计一个新方法便捷的获取分页信息。

在此介绍一个分页的思路供大家参考(团队共同的成果,笔者不敢私自占有):

综合考虑业务实际与开发的复杂程度,项目团队决定在出现跨表查询的情况下,每一张表采用一个线程进行查询,以提高查询效率。

这个方案的难点在于分页规则的转换。例如,页面传入的offset和pageSize分别为8和20。各分表中符合条件的数量分别为10,10,50。那么我们需要将总的分页条件转化为三个分表各自的分页条件,如图

e25b2952273b01aa0a46e416e9adf219.jpeg

通过上图可以看到,大分页条件(offset=8,pageSize=20),转换为(offset=8,pageSize=2),(offset=0.pageSize=10),(offset=0,pageSize=8)三个条件。

整个计算过程如下:

  1. 多线程查询各个分表中满足条件的数据数量

  2. 将各个表数量按照分表的先后顺序累加,形成图 8的数轴

  3. 判断第一条数据和最后一条数据所在的表

  4. 除第一条和最后一条数据所在表外,其他表offset=0,pageSize=总数量

  5. 计算第一条数据的offset,pageSize

计算最后一条数据的pageSize,同时将该表查询条件的offset设置为0

数据迁移方案

在数据迁移前,团队讨论过两套迁移方案:

1)请DBA迁移数据;

2)手写代码迁移数据,他们各有自己的优缺点:

1fbcfa698b98ae044b3e5f0ea1a8f19c.png

综合考虑时间成本和对线上数据库的影响,团队决定采用两种方案结合的方式:

  • 交易时间为三个月前的冷数据,由于更新几率不大,采用代码的方式迁移,人为控制每次迁移数量,少量多次,蚂蚁搬家;

  • 交易时间为三个月内的热数据,由于会在上线前频繁出现更新操作,则在上线前停止写操作,而后由DBA整体迁移。这样将时间成本平摊到平时,上线前只有约2个小时左右迁移数据时系统无法使用。

  • 同时,除了最后一次DBA迁移数据外,能够人为控制每次迁移的数据量,整体避免数据库实例级别的高延迟。

整体上线流程

为保证新表拆分功能的稳定性和大表下线的稳定,团队将整个项目分为三个阶段:

  • 第一阶段: 建立分表,大表数据迁移分表,线上数据新表老表双写,所有查询走分表(验证观察)

  • 第二阶段: 停止写老数据表,其他业务直连数据库改为资金提供对外接口(验证观察)

  • 第三阶段:大表下线

总结

  • 应再进一步调研分表相关中间件。由于项目分表依据的特殊性,导致sharding-jdbc的很多功能无法利用,其对于简化查询逻辑的帮助低于预期。并且sharding-jdbc独立数据源的特性,引发了多数据源事务问题,反而增加了开发的工作量。

  • 多线程需要仔细分析线程池核心线程的大小,同时分析多线程池同时存在的时候是否会引起核心线程数过多,避免机器线程打满。

  • 如果是一个已有的项目,在进行分表改造时,一定要将各种场景都罗列清楚,将各个场景细化到程序中的每个类、每个方法中,将所有业务场景都覆盖到。

  • 在迁移历史数据时,一定要做好迁移数据方案,以及应对出现数据不一致时的处理方案。要综合考虑时间成本、数据准确性、对线上功能的影响等诸多因素。

  • 在上线一个比较复杂的方案时,一定要提前设计好回滚方案和降级措施,能够极大保证稳定性。

说点儿题外话

为啥说想说点儿题外话呢,主要是对这次延续了5个多月的项目有感而发。项目进行过程中,难免会与其他系统的维护团队有工作上的交集,有需要其他团队配合的地方。

这个时候非常考验程序员的沟通能力,最优秀的程序员能够通过话术把对方拉到自己的阵线当中,让对方感到这项工作对自己也是有好处的。这样能够让对方心甘情愿的配合你的工作,达到双赢的目的。

如果程序设计和学习能力是程序员的硬实力,那沟通技巧就是程序员的软实力,硬实力能够保障你的下线,而决定上线的恰恰是软实力。

因此很多程序员不注重沟通技巧的培养,其实是相当于瘸腿的,毕竟现在凭单打独斗是不大可能做出事情的。

另外,至少对于我们单位来说,对后端程序员的综合素质其实要求最高。后端程序员集业务、技术于一身。需要有比较强的业务把控能力,还要有过硬的技术素质。

同时,大多数工作的主owner是后端,一般都是后端程序员把控前端、后端、QA的开发节奏,协调好各个时间点,做好风险反馈。

这就要求后端程序员既要懂业务,还要懂技术,还需要有一定的管理能力。这其实对人的锻炼还是很可观的。


欢迎加入我的知识星球,全面提升技术能力。

👉 加入方式,长按”或“扫描”下方二维码噢

d597595ee8c18e58fc98bb66462a37f9.png

星球的内容包括:项目实战、面试招聘、源码解析、学习路线。

dde6582c414a30e132ab989d518b5d10.png

e69a08b8baf4b07f60586383927b4d94.pngf66eee36314d4648fac03549c1cf47f4.png0c7682a8ad35b6796c9a073f997eccf5.png3a70b8f62a438bb033047dae100e5221.png

文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
好的,我理解了您的要求。接下来我将按照要求进行大表拆分相关问题的回答。 相关问题: 1. 为什么要进行大表拆分? 2. 大表拆分会带来哪些影响? 3. 如何确定何时适合进行大表拆分? 对于MySQL大表拆分,以下是一些常见的策略和考虑因素: 1. **垂直拆分**:将一个大表按照列的类型进行拆分,例如将一个包含大量数据的表拆分为多个包含特定列的表。这种方法适用于某些特定列的数据量过大,导致整个表的数据量过大。 2. **水平拆分**:将一个大表按照某个字段的值进行拆分,通常使用主键或唯一索引作为拆分的依据。这种方法适用于表中的数据量差异较大,需要将数据分布到不同的服务器或数据库中。 3. **使用分区**:MySQL提供了分区功能,可以将一个大表按照特定的规则分成多个分区,每个分区可以独立管理。这种方法适用于数据量非常大,需要提高查询性能和扩展性。 在进行大表拆分时,需要考虑以下因素: 1. **数据一致性**:确保拆分后的表之间数据的一致性,避免出现数据不一致的情况。 2. **性能影响**:拆分后的表可能会影响查询性能,需要评估拆分后的性能是否满足需求。 3. **备份和恢复**:在拆分之前,需要做好数据备份和恢复工作,确保数据的安全性。 4. **系统资源**:需要考虑系统资源(如CPU、内存、磁盘空间等)的限制,确保拆分后的表能够正常运行。 如果您有任何其他问题或需要进一步的解释,欢迎随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值