MySQL-分库分表方案

article/2025/4/24 10:44:39

一、业务背景

随着业务量的增长,数据量会随之增长,单机情况下DB服务器会面临存储容量、连接数和处理能力的瓶颈,当数据量达到一定量级时,DDL变更时间变长,影响业务可用性,此时需要考虑分库分表,提高SQL性能。

阿里巴巴开发手册规定:单表数据量超过500万行或者单表容量超过2GB时,需要进行分库分表。

为什么是规定500万行?取决于硬件条件:MySQL会提前加载索引到内存中,当一张表的索引太大的时候,内存不够就会进行磁盘IO,这将极大的限制整个数据库的速度。

知识扩展:MySQL表大小限制

MySQL4.0之前,单表最大限制取决于存储引擎,MyISAM支持单表最大限制为 64 PB(67108864 GB)。MySQL4.0之后,支持InnoDB引擎(MySQL5.5 之后的默认存储引擎),Innodb存储数据的策略分为两种:

共享表空间存储方式:Innodb的所有数据保存在一个单独的表空间里面,而这个表空间可以由很多个文件组成,一个表可以跨多个文件存在,所以其大小限制不是文件大小的限制,而受限于表空间。官方指出 Innodb 表空间的最大限制为 64 TB。

独享表空间存储方式:每个表的数据以一个单独的文件(创建表时会自动生成一个xxx.ibd文件)来存放,此时的单表限制,就变成文件系统的大小限制了。文件系统大小受操作系统Block大小限制,Linux操作系统(如CentOS、RedHat)使用ext3文件系统,例如一个4KB大小的块最大能存放文件大小为2TB。

二、分表方案

当单表数据量很大,严重影响业务接口响应时间时(此时MySQL实例负载可能并不高),此时只需要分表。由表容量计算公式TABLE_SIZE = AVG_ROW_SIZE x ROWS,得出分表的两种方案:垂直分表(切分字段)和水平分表(切分记录)。

垂直分表方案

1. 冷热分离

依据二八定律(帕累托法则),频繁使用的字段往往只占所有字段中的一小部分,因此可以将高频使用字段的数据放在一张表,将剩余字段的数据放在另一张表。

查询:select a.entity_id , b.path_trace from table1 a, table2 b where a.id = b.id and a.entity_id = 101

特点:查询时如果没有使用中间件,对代码的改造量较大,并且容易出错,可能实际业务用的不多。

2. 拆分大字段

如果业务表中有必须的Text类型字段,可以将Text类型字段拆分到子表中进行存储。

查询:select a.seq_id, b.comment from table1 a, table2 b where a.id = b.id and a.seq_id = 101;

特点:大字段单独存储,提高sql查询性能。

水平分表方案

1. 按ID分表

依据业务的增长情况,估算一年后的数据量,将整个数据表拆分为n个子表(参考单表数据不超过500w行规定),拆分过程使用ID取模法,即id % n,将每个数据行拆到对应的子表中。

2. 按时间分表

根据业务规模,将时间作为粒度对表进行拆分。

每日表:只存储当天数据。

每月表:起一个定时任务将前一天的数据全部迁移到当月表中。

历史表:同样使用定时任务将超过30天的数据全部迁移到历史表中。

举例,按月维度分表:财务或者计费类系统会在月底统计当月账单,相关的业务执行完后表中的数据是静态化了,之后不会使用到这些数据,当月账单出完后,可以归档到历史库,供数仓ETL做分析报表,确认数据同步到历史库后库,可以从业务库中删除已同步月份的数据,释放表空间。

MySQL分区表

思路和按ID分表相近,只是使用MySQL内部已有的分区概念,基于HASH分区对分区个数进行取模运算。案例:

CREATE TABLE orders (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT'id',order_id varchar(20) NOT NULL COMMENT '订单ID',user_id bigint(20) NOT NULL COMMENT '用户ID',PRIMARY KEY(id, user_id), #分区键必须包含在主键中KEY idx_user_id(user_id)
)
PARTITION BY HASH(user id) #使用哈希分区,分区键user_id
PARTITIONS 20;  #20个分区
insert into orders(order_id, user_id) values('001',1000),('002',1001),('003',1002),('004',1003),('020',1019);

这样就创建了20个分区,对应磁盘上就是20个数据文件(orders#p#p0.ibd一直到orders#p#p19.ibd),来看一下SQL的执行过程。

从执行计划可以看到,通过分区键user_id过滤,直接可以定位到数据所在的分区 p19(user_id =1019 % 20 = 19,所以在p19分区上),进而去访问p19对应的数据文件 orders#p#p19.ibd 即可获得数据。

特点:使用MySQL内部实现 SQL 路由的功能,不用去改造业务代码。

三、分库方案

主从架构下,写操作都发生在Master节点,随着业务增长,很多接口RT变长,甚至超时,此时需要通过分库抗高并发。通常也有两种做法:按业务分库和按表分库

1. 按业务分库

按业务分库其实是做服务拆分,在系统业务量很大的情况下,需求和功能会越来越多,此时需要考虑根据业务类型进行分库,比如将库存、交易、支付相关的接口独立开来。

特点:一个库被分成多个库,数据库写入能力提升,接口相应时间变短,同时提高了系统的稳定性。

2. 按表分库

基于垂直分表和水平分表,产生相应的垂直分库和水平分库,即将拆分后的子表存储在拆分后的子库中。垂直分库应用的较少,主要是水平分库。此时可以按ID进行分表,然后将相应的表划分到不同的RDS实例中。

具体查询时候,可以通过ID定位到时那个RDS实例,然后定位到具体的子表。

四、数据库中间件

目前市面上比较常见的数据库中间件按所属层级,主要分为proxy层类型的中间件和client类型的中间件。

  • proxy层类型的中间件

该类型的数据库中间件单独部署在一台服务器上,它的优点是,对于系统来说透明,如果遇到中间件需要升级,只需要修改中间件层,缺点也比价明显,数据库中间件单独部署,增加了运维成本。

  • client层类型的中间件

使用该类型的中间件,我们只需要在系统中引入一个jar包即可使用,无需额外部署,降低了运维成本,无需代理转发,但它的缺点是,当遇到版本升级时,需要每个系统都升级一遍。

常见的数据库中间件

目前比较常见的中间件包括以下几种:

  • Cobar,阿里B2B团队开源,proxy层方案
  • TDDL,淘宝团队,client层方案
  • Atlas,360开源,proxy层方案
  • Sharding-jdbc,当当开源,client层方案,支持分库分表、读写分离、分布式id生成
  • Mycat,基于Cobar改造,proxy层方案,支持的功能非常完善,相比sharding-jdbc,只是缺少时间的历练

Cobar

Cobar中间件,属于 proxy 层方案,就是介于应用服务器和数据库服务器之间。应用程序通过 JDBC 驱动访问 Cobar 集群,Cobar 根据 SQL 和分库规则对 SQL 做分解,然后分发到 MySQL 集群不同的数据库实例上执行。该中间件早些年还在用,最近已经没什么人使用了,不支持读写分离、存储过程、跨库 join 和分页等操作。

TDDL

淘宝团队开发,属于 client 层方案。支持基本的 crud 语法和读写分离,但不支持 join、多表查询等语法。目前使用的也不多,因为还依赖淘宝的配置管理系统(diamond)。

Atlas

360 开源,属于 proxy 层方案,以前是有一些公司在用,但是现在用的公司较少了。

Sharding-jdbc

当当开源,属于 client 层方案,目前已经更名为 ShardingSphere。支持分库分表、读写分离、分布式 id 生成、柔性事务(最大努力通知事务、TCC 事务)。而且确实之前使用的公司会比较多一些,目前社区也还一直在开发和维护,还算是比较活跃,是一个可选方案。

小案例:Sharding-jdbc分库分表实践

参考:ShardingJDBC的分库分表实践 - 掘金

订单服务表t_order业务量比较大,现需要进行分库分表

  • 分库:根据主键id的奇偶性进行分库,奇数id在ds0,偶数id在ds1
  • 分表:根据day_date数值是2022还是2023进行分表

数据库配置

分别建两张表t_order_2022,t_order_2023到两个RDB实例的数据库

CREATE TABLE `t_order2022` (`id` bigint(32) NOT NULL,`user_id` int(11) DEFAULT NULL,`order_id` int(11) DEFAULT NULL,`cloumn` varchar(45) DEFAULT NULL,`day_date` char(8) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `t_order2023` (`id` bigint(32) NOT NULL,`user_id` int(11) DEFAULT NULL,`order_id` int(11) DEFAULT NULL,`cloumn` varchar(45) DEFAULT NULL,`day_date` char(8) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

项目依赖

<dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-jdbc-spring-boot-starter</artifactId><version>${sharding-sphere.version}</version>
</dependency>

配置文件

server.port=10080spring.shardingsphere.datasource.names=ds0,ds1# 配置第一个数据库
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/ds0
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456# 配置第二个数据库
spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3306/ds1
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=123456# 配置t_order表的分库策略spring.shardingsphere.sharding.tables.t_order.database-strategy.standard.sharding-column=id# 自定义分库策略
spring.shardingsphere.sharding.tables.t_order.database-strategy.standard.precise-algorithm-class-name=com.example.shardingjdbc.config.MyDbPreciseShardingAlgorithm# 配置t_order的分表策略
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ds$->{0..1}.t_order$->{2022..2023}
spring.shardingsphere.sharding.tables.t_order.table-strategy.standard.sharding-column=day_date# 自定义分表策略
spring.shardingsphere.sharding.tables.t_order.table-strategy.standard.precise-algorithm-class-name=com.example.shardingjdbc.config.MyTablePreciseShardingAlgorithm# 添加t_order表的id生成策略
spring.shardingsphere.sharding.tables.t_order.key-generator.column=id
spring.shardingsphere.sharding.tables.t_order.key-generator.type=SNOWFLAKE# 打开sql输出日志
spring.shardingsphere.props.sql.show=true# mybatis配置
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.example.shardingjdbc.po# 配置日志级别
logging.level.com.echo.shardingjdbc.dao=DEBUG

application.properties配置

tables:t_order:actualDataNodes: ds$->{0..1}.t_order$->{20212..2023}databaseStrategy:standard:preciseAlgorithmClassName: com.example.shardingjdbc.config.MyDbPreciseShardingAlgorithmshardingColumn: idkeyGenerator:column: idtype: SNOWFLAKElogicTable: t_ordertableStrategy:standard:preciseAlgorithmClassName: com.example.shardingjdbc.config.MyTablePreciseShardingAlgorithmshardingColumn: day_date

自定义分库分表规则类

package com.example.shardingjdbc.config;import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;import java.util.Collection;
@Slf4j
public class MyDbPreciseShardingAlgorithm implements PreciseShardingAlgorithm<Long>{/*** 分片策略* @param availableTargetNames 所有的数据源* @param preciseShardingValue SQL执行时传入的分片值* @return 返回*/@Overridepublic String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> preciseShardingValue) {//真实节点availableTargetNames.forEach(a -> log.info("actual node db:{}", a));log.info("logic table name:{}, route column:{}" , preciseShardingValue.getLogicTableName(), preciseShardingValue.getColumnName());//精确分片log.info("column name:{}", preciseShardingValue.getValue());for (String availableTargetName : availableTargetNames) {Long value = preciseShardingValue.getValue();if (("ds"+value%2).equals(availableTargetName)) {return availableTargetName;}}return null;}
}
package com.example.shardingjdbc.config;import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;import java.util.Collection;@Slf4j
public class MyTablePreciseShardingAlgorithm implements PreciseShardingAlgorithm<String>{/*** 自定义分表规则* @param availableTargetNames* @param preciseShardingValue* @return*/@Overridepublic String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> preciseShardingValue) {//真实节点availableTargetNames.forEach(a -> log.info("actual node table:{}", a));log.info("logic table name:{}, route column:{}", preciseShardingValue.getLogicTableName(), preciseShardingValue.getColumnName());//精确分片log.info("column value:{}", preciseShardingValue.getValue());for (String availableTargetName : availableTargetNames) {if (("t_order"+preciseShardingValue.getValue()).equals(availableTargetName)) {return availableTargetName;}}return null;}
}

Mycat

基于 Cobar 改造的,属于 proxy 层方案,支持的功能非常完善,而且目前应该是非常火的而且不断流行的数据库中间件,社区很活跃,也有一些公司开始在用,相比于 Sharding jdbc 来说,年轻一些,经历的锤炼少一些。

小结

  • 中小型公司选用 Sharding-jdbc,client 层方案轻便,而且维护成本低,不需要额外增派人手,而且中小型公司系统复杂度会低一些,项目也没那么多;
  • 中大型公司最好还是选用 Mycat 这类 proxy 层方案,因为可能大公司系统和项目非常多,团队很大,人员充足,那么最好是专门指定一些人研究和维护 Mycat,然后大量项目直接透明使用即可。

五、分库后面临的问题

1. 跨库Join问题

分库之后,数据库分布在不同RDS实例中,根据MySQL开发规范,一般是禁止跨库Join。通常采用全局表数据同步方案解决垮库Join问题。

  • 全局表

使用MyCat做分库分表,有一个全局表概念,每个DataNode上有一份全量数据,例如一些数据字典表,数据很少修改,可以避免垮库Join的问题。

  • 数据同步

将一份RDS实例上的数据同步到另一份上,解决垮库 Join问题。比较依赖同步工具的稳定性,如果同步有延迟会导致数据不一致,产生脏数据,需要做好风险评估和兜底。

2. 分布式事务

分布式事务常用的解决方案:

  • 两阶段提交
  • TCC
  • 本地消息表(业界使用较多)
  • 可靠消息最终一致性
  • 最大努力通知

可靠消息最终一致性方案图

3.分布式全局唯一ID

如果每个拆分后的RDS实例使用自增ID作为主键,则会出现ID重复的问题,可以使用Snowflake算法生成唯一ID。

4.垮库函数处理

使用max、min、sum等进行统计和计算时,需要再每个分片数据源上进行相应的函数处理,然后将各个结果集进行二次处理,最终将处理结果返回。

六、总结

在实际开发过程中,遇到核心业务表增长过快的情况下,如果执行SQL语句出现了性能瓶颈,考虑分库分表。拆分策略需要结合具体的应用场景,选择合适的方案,从而支撑业务的快速增长和系统的快速迭代。


http://chatgpt.dhexx.cn/article/LsWB5K5F.shtml

相关文章

最全的MySQL分库分表方案总结

“ 面试中我们经常会碰到的关于分库分表的问题&#xff01;今天就给大家介绍互联网公司常用 MySQL 分库分表方案&#xff01;希望对大家的面试有所帮助&#xff01; 数据库瓶颈 不管是 IO 瓶颈&#xff0c;还是 CPU 瓶颈&#xff0c;最终都会导致数据库的活跃连接数增加&#x…

python mysql分库分表_干货 : 常用MySQL分库分表方案

Python乱炖推荐搜索后浪 动森玩家 送书 数据分析 一、数据库瓶颈 不管是IO瓶颈,还是CPU瓶颈,最终都会导致数据库的活跃连接数增加,进而逼近甚至达到数据库可承载活跃连接数的阈值。在业务Service来看就是,可用数据库连接少甚至无连接可用。接下来就可以想象了吧(并发量、吞…

php分库分表技术,谈谈关于分库分表的方案

1. 数据库瓶颈 不管是IO瓶颈,还是CPU瓶颈,最终都会导致数据库的活跃连接数增加,进而逼近甚至达到数据库可承载活跃连接数的阈值。在业务Service来看就是,可用数据库连接少甚至无连接可用。接下来就可以想象了吧(并发量、吞吐量、崩溃)。 1.1. IO瓶颈 第一种:磁盘读IO瓶颈,…

这应该是全网最全的分库分表方案了

一、数据库瓶颈 不管是IO瓶颈&#xff0c;还是CPU瓶颈&#xff0c;最终都会导致数据库的活跃连接数增加&#xff0c;进而逼近甚至达到数据库可承载活跃连接数的阈值。在业务Service来看就是&#xff0c;可用数据库连接少甚至无连接可用。接下来就可以想象了吧&#xff08;并发…

MySQL第六讲 MySQL分库分表方案

分库分表概念 分库分表就是业务系统将数据写请求分发到master节点&#xff0c;而读请求分发到slave 节点的一种方案&#xff0c;可以大大提高整个数据库集群的性能。但是要注意&#xff0c;分库分表的 一整套逻辑全部是由客户端自行实现的。而对于MySQL集群&#xff0c;数据主从…

分库分表方案对比

房晓乐&#xff08;葱头巴巴&#xff09;&#xff0c;PingCAP 资深解决方案架构师&#xff0c;前美团数据库专家、美团云 CDS 架构师、前搜狗、百度资深 DBA&#xff0c;擅长研究各种数据库架构&#xff0c;NewSQL 布道者。 原文链接&#xff1a;https://dbaplus.cn/news-11-1…

分库分表方案

一、为什么要进行分库分表 当MySQL单表数据量过大&#xff0c;比如超过5千万条的时候&#xff0c;读写性能变得很差。而且常规的优化手段已经不起作用了&#xff0c;比如&#xff1a;SQL调优、添加索引、主从复制、读写分离。这时候就需要用到MySQL终极优化方案 — 分库分表。 …

分库分表设计方案

一、为什么要分库分表? 随着业务的不断发展&#xff0c;数据量不断增加&#xff0c;因此数据操作&#xff0c;如增删改查的开销也会越来越大&#xff0c;原来基于单库单表的设计已经不能满足存储需求&#xff0c;数据库随时面临爆库风险&#xff1b; 再加上物理服务器的资源有…

python:numpy的corrcoef计算相关系数

corrcoef(x, yNone, rowvarTrue, biasnp._NoValue, ddofnp._NoValue)函数常用的是前三个参数&#xff0c;x和y分别是需要计算相关系数的两个随机变量&#xff0c;当rowvar为True(默认情况)时&#xff0c;每一行代表一个随机变量&#xff0c;否则每一列代表一个随机变量。 该函数…

python 计算相关性系数np.corrcoef()

计算相关性是分析连续型与连续型双变量的常用方法&#xff0c;散点图只能直观的显示双变量&#xff08;特征&#xff09;之间的关系&#xff0c;但并不能说明关系的强弱&#xff0c;而相关性可以对变量之间的关系进行量化分析。 相关性系数的公式如下&#xff1a; 相关性系数…

Numpy库 numpy.corrcoef()函数

相关系数公式&#xff1a; 其他详见&#xff1a; 1. Python Numpy库 numpy.corrcoef()函数讲解 2. 协方差、方差、标准差、协方差系数 3. 标准差、方差、协方差三者的表示意义

MATLAB中的corrcoef函数求两个向量的相关系数。

&#xfeff;&#xfeff; 想用MATLAB中的corrcoef函数求两个向量的相关系数。 比如A[1 2 3];B[5 3 7]; r corrcoef(A,B)可以求出相关系数是0.5.为什么两个向量的元素都要是3个以上才行&#xff1f;而只有两个元素的向量如A[1 2];B[5 3];不管怎么随机的取&#xff0c;相关系数都…

使用numpy计算相关系数矩阵:np.corrcoef()

【小白从小学Python、C、Java】 【Python-计算机等级考试二级】 【Python-数据分析】 使用numpy计算相关系数矩阵 np.corrcoef() 选择题 关于以下代码说法错误的是? import numpy as np array1np.array([[1,2,3], [2,3,4], [2,3,3], [4,3,2], [4,3,3]]) print(array1) print(&…

matlab 计算相关系数,MATLAB如何使用corrcoef函数计算样本数据的相关系数

MATLAB如何使用corrcoef函数计算样本数据的相关系数 【语法说明】 Rcorrcoef(X)&#xff1a;如果X为向量&#xff0c;函数返回1&#xff1b;如果X为mn 矩阵&#xff0c;则以每行为观测值&#xff0c;每列为一个随机变量计算相关系数&#xff0c;返回一个nn对称矩阵。假设协方差…

numpy.corrcoef()函数讲解

numpy.corrcoef(x, y无, rowvar True, 偏差<无值>, ddof<无值>) 【学习参考】&#xff1a;Python Numpy库 numpy.corrcoef()函数讲解_Hello_xzy_Word的博客-CSDN博客_numpy.corrcoef x&#xff1a; array_like&#xff0c;包含多个变量和观测值的1-D或2-D数组&a…

Python Numpy库 numpy.corrcoef()函数讲解

例子&#xff1a; 代码&#xff1a; import numpy as npArray1 [[1, 2, 3], [4, 5, 6]] Array2 [[11, 25, 346], [734, 48, 49]] Mat1 np.array(Array1) Mat2 np.array(Array2) correlation np.corrcoef(Mat1, Mat2) print("矩阵1\n", Mat1) print("矩阵…

一、求相关函数 corrcoef+协方差矩阵cov

1. 求相关函数corrcoef 一般作用&#xff1a;1&#xff09;结合图表评估数据拟合效果&#xff1b;2&#xff09;评估两组数据之间的互相关联程度&#xff0c;大于0正相关&#xff0c;小于0负相关&#xff0c;等于0不相关。 但此种计算方法反映的是“线性相关”程度&#xff0…

MATLAB 协方差 [cov] 和相关系数 [corrcoef] 说明

A,B为两个长度相同的向量 求协方差 Scov(A,B); b和c数值上是相等的。 求相关系数 Rcorrcoef(A,B) 数值上,f和g是相同到。 相关系数存在许多种类&#xff0c;上述corrcoef 指 pearson correlation coefficient。 性质&#xff1a;向量乘常数&#xff0c;不改变Pearson 相关…

皮尔逊相关分析的MATLAB实现,corr(),corrcoef()函数使用

皮尔逊相关系数可以用来表述两个序列的相关性。 常常用来做数据分析、数据挖掘等工作。 如何简单快捷的实现皮尔逊相关&#xff0c;并得到结果&#xff1f; 需要应用MATLAB中的corr(X, Y)或者 corrcoef(X,Y)函数。 其中corr(X, Y)既可以计算矩阵相关也可以计算序列相关&…

用C语言实现简单的计算器

功能介绍 用C语言实现整形简单的加减乘除操作&#xff08;通常方法与使用函数指针数组的方法&#xff09; 代码实现 方法一 #define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> #include<stdlib.h>int main() {while (1){double x 0, y 0, ret 0;//定义运…