《MySQL 亿级数据表平滑分表实践:基于时间分片的架构演进》

《MySQL 亿级数据表平滑分表实践:基于时间分片的架构演进》
个人名片

🎓作者简介:java领域优质创作者
🌐个人主页码农阿豪
📞工作室:新空间代码工作室(提供各种软件服务)
💌个人邮箱:[[email protected]]
📱个人微信:15279484656
🌐个人导航网站:www.forff.top
💡座右铭:总有人要赢。为什么不能是我呢?
  • 专栏导航:
码农阿豪系列专栏导航
面试专栏:收集了java相关高频面试题,面试实战总结🍻🎉🖥️
Spring5系列专栏:整理了Spring5重要知识点与实战演练,有案例可直接使用🚀🔧💻
Redis专栏:Redis从零到一学习分享,经验总结,案例实战💐📝💡
全栈系列专栏:海纳百川有容乃大,可能你想要的东西里面都有🤸🌱🚀

目录

《MySQL 亿级数据表平滑分表实践:基于时间分片的架构演进》

引言

在互联网应用快速发展的今天,数据量呈现爆炸式增长。作为后端开发者,我们常常会遇到单表数据量过亿导致的性能瓶颈问题。本文将以一个真实的4亿数据表分表案例为基础,详细介绍如何在不影响线上业务的情况下,完成按时间维度分表的完整过程,包含架构设计、具体实施方案、Java代码适配以及注意事项等全方位内容。

一、为什么我们需要分表?

1.1 单表数据量过大的问题

当MySQL单表数据量达到4亿级别时,会面临诸多挑战:

  • 索引膨胀,B+树层级加深,查询效率下降
  • 备份恢复时间呈指数级增长
  • DDL操作(如加字段、改索引)锁表时间不可接受
  • 高频写入导致锁竞争加剧

1.2 分表方案选型

常见的分表策略有:

  1. 水平分表 :按行拆分,如按ID范围、哈希、时间等
  2. 垂直分表 :按列拆分,将不常用字段分离
  3. 分区表 :MySQL内置分区功能

本文选择 按时间水平分表 ,因为:

  • 业务查询大多带有时间条件
  • 天然符合数据冷热特征
  • 便于历史数据归档

二、分表前的准备工作

2.1 数据评估分析

-- 分析数据时间分布SELECT DATE_FORMAT(create_time,'%Y-%m')ASmonth,COUNT(*)AS count FROM original_table GROUPBYmonthORDERBYmonth;

2.2 分表命名规范设计

制定明确的分表命名规则:

  • 主表:original_table
  • 月度分表:original_table_202301
  • 年度分表:original_table_2023
  • 归档表:archive_table_2022

2.3 应用影响评估

检查所有涉及该表的SQL:

  • 是否都有时间条件
  • 是否存在跨时间段的复杂查询
  • 事务是否涉及多表关联

三、分表实施方案详解

3.1 方案一:平滑迁移方案(推荐)

第一步:创建分表结构
-- 创建2023年1月的分表(结构完全相同)CREATETABLE original_table_202301 LIKE original_table;-- 为分表添加同样的索引ALTERTABLE original_table_202301 ADDINDEX idx_user_id(user_id);
第二步:分批迁移数据

使用Java编写迁移工具:

publicclassDataMigrator{privatestaticfinalintBATCH_SIZE=5000;publicvoidmigrateByMonth(String month)throwsSQLException{String sourceTable ="original_table";String targetTable ="original_table_"+ month;try(Connection conn = dataSource.getConnection()){long maxId =getMaxId(conn, sourceTable);long currentId =0;while(currentId < maxId){String sql =String.format("INSERT INTO %s SELECT * FROM %s "+"WHERE create_time BETWEEN '%s-01' AND '%s-31' "+"AND id > %d ORDER BY id LIMIT %d", targetTable, sourceTable, month, month, currentId,BATCH_SIZE);try(Statement stmt = conn.createStatement()){ stmt.executeUpdate(sql); currentId =getLastInsertedId(conn, targetTable);}Thread.sleep(100);// 控制迁移速度}}}}
第三步:建立联合视图
CREATEVIEW original_table_unified ASSELECT*FROM original_table_202301 UNIONALLSELECT*FROM original_table_202302 UNIONALL...SELECT*FROM original_table;-- 当前表作为最新数据

3.2 方案二:触发器过渡方案

对于不能停机的关键业务表:

-- 创建分表CREATETABLE original_table_new LIKE original_table;-- 创建触发器DELIMITER//CREATETRIGGER tri_original_table_insert AFTERINSERTON original_table FOR EACH ROWBEGINIF NEW.create_time >='2023-01-01'THENINSERTINTO original_table_new VALUES(NEW.*);ENDIF;END//DELIMITER;

四、Java应用层适配

4.1 动态表名路由

实现一个简单的表名路由器:

publicclassTableRouter{privatestaticfinalDateTimeFormatterMONTH_FORMAT=DateTimeFormatter.ofPattern("yyyyMM");publicstaticStringrouteTable(LocalDateTime createTime){String month = createTime.format(MONTH_FORMAT);return"original_table_"+ month;}}

4.2 MyBatis分表适配

方案一:动态SQL
<selectid="queryByTime"resultType="com.example.Entity"> SELECT * FROM ${tableName} WHERE user_id = #{userId} AND create_time BETWEEN #{start} AND #{end} </select>
publicList<Entity>queryByTime(Long userId,LocalDate start,LocalDate end){List<String> tableNames =getTableNamesBetween(start, end);return tableNames.stream().flatMap(table -> mapper.queryByTime(table, userId, start, end).stream()).collect(Collectors.toList());}
方案二:插件拦截(高级)

实现MyBatis的Interceptor接口:

@Intercepts(@Signature(type=StatementHandler.class, method="prepare", args={Connection.class,Integer.class}))publicclassTableShardInterceptorimplementsInterceptor{@OverridepublicObjectintercept(Invocation invocation)throwsThrowable{BoundSql boundSql =((StatementHandler)invocation.getTarget()).getBoundSql();String originalSql = boundSql.getSql();if(originalSql.contains("original_table")){Object param = boundSql.getParameterObject();LocalDateTime createTime =getCreateTime(param);String newSql = originalSql.replace("original_table","original_table_"+ createTime.format(MONTH_FORMAT));resetSql(invocation, newSql);}return invocation.proceed();}}

五、分表后的运维管理

5.1 自动建表策略

使用Spring Scheduler实现每月自动建表:

@Scheduled(cron ="0 0 0 1 * ?")// 每月1号执行publicvoidautoCreateNextMonthTable(){LocalDate nextMonth =LocalDate.now().plusMonths(1);String tableName ="original_table_"+ nextMonth.format(MONTH_FORMAT); jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS "+ tableName +" LIKE original_table_template");}

5.2 数据归档策略

publicvoidarchiveOldData(int keepMonths){LocalDate archivePoint =LocalDate.now().minusMonths(keepMonths);String archiveTable ="archive_table_"+ archivePoint.getYear();// 创建归档表 jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS "+ archiveTable +" LIKE original_table_template");// 迁移数据 jdbcTemplate.update("INSERT INTO "+ archiveTable +" SELECT * FROM original_table WHERE create_time < ?", archivePoint.atStartOfDay());// 删除原数据 jdbcTemplate.update("DELETE FROM original_table WHERE create_time < ?", archivePoint.atStartOfDay());}

六、踩坑与经验总结

6.1 遇到的典型问题

  1. 跨分页查询问题 :
  • 解决方案:使用Elasticsearch等中间件预聚合
  1. 分布式事务问题 :
  • 解决方案:避免跨分表事务,或引入Seata等框架
  1. 全局唯一ID问题 :
  • 解决方案:使用雪花算法(Snowflake)生成ID

6.2 性能对比数据

指标分表前分表后
单条查询平均耗时320ms45ms
批量写入QPS1,2003,500
备份时间6小时30分钟

七、未来演进方向

  1. 分库分表 :当单机容量达到瓶颈时考虑
  2. TiDB迁移 :对于超大规模数据考虑NewSQL方案
  3. 数据湖架构 :将冷数据迁移到HDFS等存储

结语

MySQL分表是一个系统工程,需要结合业务特点选择合适的分片策略。本文介绍的按时间分表方案,在保证业务连续性的前提下,成功将4亿数据表的查询性能提升了7倍。希望这篇实践总结能为面临类似问题的开发者提供有价值的参考。

作者提示:任何架构改造都要先在测试环境充分验证,并准备好回滚方案!

Read more

VS code 安装依赖包出现的一系列报错:node.js

VS code 安装依赖包出现的一系列报错:node.js

场景: VS code安装依赖包的一系列报错 问题描述 问题1:VS code使用npm install 安装包的时候,出现 **npm:无法将“npm项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。 所在位置 行:1 字符:1 +npm i +CategoryInfo:ObjectNotFound:(npm:String) (],CommandNotFoundException 4FullyQualifiedErrorId :CommandNotFoundException** 因为没有安装node.js,所以识别不了,下面安装node.js: 参考https://blog.ZEEKLOG.net/weixin_42182741/article/details/152072795 感谢大佬提供的详细教程! 安装之后,

By Ne0inhk
测试飞快、生产卡死:你的 SQL 性能瓶颈,被这项技术破解了

测试飞快、生产卡死:你的 SQL 性能瓶颈,被这项技术破解了

每日一句正能量 >无论生活怎样,希望你保持自律,保持清醒,昨日之深渊,今日之浅淡。敬大家也敬自己! 前言 你是否遇到过这样的场景:一个看似复杂的SQL,在测试环境运行飞快,一到生产环境就“卡死”,一查执行计划,发现子查询生成了一个巨大的中间结果集,导致后续操作全部陷入性能泥潭? 如果你正被此类场景困扰,那么,是时候认识一项改变游戏规则的技术:金仓数据库(KingbaseES)「基于代价的连接条件下推」。它不仅是技术优化,更是应对复杂业务查询的“性能终结者”。 一、 为什么你的复杂SQL会“爆内存”? 在金融、政务等复杂业务系统中,为了逻辑清晰,SQL常常被写成这样: SELECT * FROM (SELECT DISTINCT * FROM 巨表_A) AS 子查询结果,      筛选表_B WHERE 子查询结果.关键ID = 筛选表_B.关键ID

By Ne0inhk
SQL Server 2025安装教程

SQL Server 2025安装教程

软件介绍 SQL Server是由微软推出的关系型数据库管理系统,它提供了可靠的数据存储、数据管理和数据分析功能。SQL Server支持多种数据处理功能,包括事务处理、数据分析、报表生成和数据挖掘等,因此在企业和组织中得到广泛应用。 软件安装包领取 感兴趣可直接无套路获取 【SQL Server 2025】安装包 无需转发,无套路,即可领取 👇👇👇 (仅作学术分享,不可商用,支持正版) 软件安装流程 1.打开文件夹,点开文件夹,鼠标右键,以管理员的身份运行 2.点击【安装】,再点击第一个; 3.点击 4.先勾选,再点击 5.点击 6.点击 7.不要勾选,再点击 8.先勾选,确认好想要安装的途径,再点击 9.

By Ne0inhk
KingbaseES数据库:ksql 命令行从建表到删表实战(含避坑指南)

KingbaseES数据库:ksql 命令行从建表到删表实战(含避坑指南)

KingbaseES数据库:ksql 命令行从建表到删表实战(含避坑指南) 本文围绕 KingbaseES 中 ksql 命令行操作表展开,涵盖表 “创建→查看→数据增删改查→结构修改→删除” 全生命周期。前置准备需连接数据库并切换目标模式,可选确认表空间;创建表要明确数据类型与约束,含基础单表、进阶复合主键表示例;查看表结构可通过 \dt、\d、\d + 命令分别获取表列表、基本结构、详细信息;数据操作聚焦 INSERT(单条 / 批量)、SELECT(全量 / 条件 / 排序)、UPDATE(必加 WHERE)、DELETE(必加 WHERE);结构修改含增列、改列、删列、加约束、改表名;还梳理了三类常见报错解决方案。全文搭配实操命令与验证方法,助力新手快速掌握表操作核心技巧。

By Ne0inhk