如果数据量过大,大家一般会分库分表。分库需要注意的内容比较少,但分表需要注意的内容就多了。
工作这几年没遇过数据量特别大的业务,那些过亿的数据,因为索引设置合理,单表性能没有影响,所以实战中一直没用过分表。最近手里有个项目,预估数据量会很大,分表方案是选项之一,趁着这次机会,把分表的内容整理一下。
这次要讲的主要是水平分库分表,其它种类的分库、分表比较容易理解。后面如果不详细说明,都指水平分库分表。
1.基础知识
1.1分库分表定义
1.1.1分库
垂直分库:按照业务模块进行切分,将不同模块的表切分到不同的数据库中。
如电商系统有电商数据库,按照业务模块可以分为用户库、商品库、订单库,这些都可以当做独立数据库,不需要放到一起。好处是既能独立变更,又能隔绝相互影响。
1.1.2分表
垂直分表:也就是“大表拆小表”,基于列字段进行的。一般是因为表设计不合理,需要进行拆分。
如一张表存放学生、老师、课程、成绩信息,最好拆分为学生表、课程表、成绩表。
水平分表:针对数据量巨大的单张表(比如订单表),按照某种规则(RANGE,HASH取模等),切分到多张表里面去。 但是这些表还是在同一个库中,所以库级别的数据库操作还是有IO瓶颈。不建议采用。
水平分库分表:将单张表的数据切分到多个服务器上去,每个服务器具有相应的库与表,只是表中数据集合不同。 水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈。
1.2分区与分片的区别
分表时经常能看到两个名词:分区和分片。这两个词都是指将大表的数据分成多块,但两者还是有本质区别的。
Sharding(分片) 的思想从分区的思想而来,但数据库分区基本上是数据对象级别的处理,比如表和索引的分区,每个子数据集上能够有不同的物理存储属性,还是单个数据库范围内的操作,而 Sharding 是能够跨数据库,甚至跨越物理机器的。
MySQL5.1提供的分区(Partition)功能确实可以实现表的分区,但是这种分区是局限在单个数据库范围里的,它不能跨越服务器的限制。
我们在分表的时候,一般使用的是分片方案,即数据存放在多个物理机器。
1.3分片策略
分片规则一般有如下:
1.3.1按照哈希切片
-
mod-long:用于分区列为数值的hash分区
分片列 id=分区列值 mod 分片数
-
mod-long-by-hash:用于分区列为字符串的hash分区
分片列id=hash(分区列值) mod 分片数
1.3.2按照范围切片
-
range:建表时创建分区规则,根据分区规则就可以确定分区列的值在哪个分区上
一般分区列为时间或者数值,如
date_range: 0: 1000000 1: 2000000 2: 3000000 3: 4000000 4: maxvalue
如果分区列值为1500000,则数据放到1号分片上。
2.分库分表中间件
需要使用者感知不到这是分片表,使用时需要和正常表一样,一般需要引入中间件。
操作分片表一般有三种方式:
2.1客户端分片
所谓的客户端分片即在使用数据库的应用层直接操作分片逻辑,分片规则需要在同一个应用的多个节点间进行同步,每个应用层嵌入一个操作切片的逻辑实现。如当当网的Sharding JDBC。
2.2代理分片
代理分片就是在应用层和数据库层之间添加一个代理层,把分片的路由规则配置在代理层,代理层对外提供与JDBC兼容的接口给应用层,在业务实现之后,在代理层配置路由规则即可。如Mycat就是基于此种解决方案来实现的。
2.3支持事务的分布式数据库
支持分布式事务的框架,目前有OceanBase、TiDB框架,这些框架将可伸缩特定和分布式事务的实现包装到了分布式数据库内部实现,对使用者透明,使用者不需要直接控制这些特性,但是对事务的支持不如关系型数据,适合大数据日志系统、统计系统、查询系统、社交网站等。
2.4说明
支持事务的分布式数据库算另一种选型了,和MySQL已经没有关系。
对于客户端分片和代理分片,目前工作过的两家公司用的都是代理方式,一家用的是MyCAT,一家用的Dbatman。客户端分片方式没有接触过。这两种的区别为:
3.分布式事务
分片意味数据分布在多台物理机器上,引入分布式事务问题。我们将单表的数据切片后存储在多个数据库甚至是多个数据库实例中,所以依靠数据库本身的事务机制不能满足需要,这时就需要用到分布式事务来解决。关于分布式事务的相关内容可以看分布式系统与一致性协议。
这里不细讲分布式事务如何处理,后面会单独写篇文章。我们聊一下分布式事务会对操作MySQL产生什么影响。
既然知道引入了分布式事务问题,那么操作MySQL的时候,肯定不能和单表一样进行操作。不同中间件能力不一样,所以需要单独分析,我以Dbatman为例,阐述使用上的区别。
- 分片版本不维护自增与主键唯一,业务可自行维护唯一键
- 意味不同分片的主键id会重复
- 不支持跨分片事务写,可以跨分片事务读
- 如果确保事务操作的内容在一个分片内,就不是分布式事务,和单机行为一致
- 一个事务涉及多个分片叫跨节点事务,单分片事务支持
- update和insert必须带分片列
总结一下就是操作同一个分片没影响,操作不同分片需要看中间件支持不支持。
4.是否选择分库分表
选择做分库分表,考虑的几个要素是:
- 空间方面:单个物理实例无法支撑数据存储需求,单台物理机无法继续通过加盘的方式扩容
- 主库性能:受限于单个主库的CPU/内存/磁盘IOPS的影响,接近或者达到上限后,需要拆分
- 容灾方面:减少单个主库宕机对于写入的影响。
针对以上3点,我们还可以多考虑一下,有没有更合适的方案
-
空间方面:
- 删除历史数据清空空间
- 修改存储模型降低对于MySQL磁盘的占用
- 改用空间压缩比更高的存储引擎
-
主库性能:
- 可以通过读写分离的方式,降低对于写库的读请求量,从而提升对于写入的支撑。
- 优化数据写入模型,减少批量写入(削峰)
-
容灾方面:
- 如果业务对于读高可用要求比较高,一般建议是做读写分离,将重要的请求路由至读库。读库数量一般会比写库多N个,在代理层面会做容灾的自动切换。
- 从集群整体的角度看,分库分表实际上是会扩大故障率,假设单台物理机的SLA是99.99%,那么2台物理机的SLA就是99.98(约数), 10台物理机的SLA就只剩下99.90%了。平均每年的故障时间也会从52分钟提升到525分钟。所以在有些场景下,单个节点故障可能会导致代理整个不可用,从而放大故障的影响范围。
5.设计
现在项目需求为:
- 生成唯一码,码值为整数
- 码值需要批量插入数据库
- 对码的更新操作都是单条处理,而且对码值进行操作需要进行记录
- 最终数量不定,长远看数据量会很大
基于上面的需求,做如下设计:
- 以码值为主键,自己控制主键唯一
- 码表使用range进行分片,如分片范围为01亿,1亿2亿
- 码表的操作记录表同样使用range进行分片,分片范围和码表一致
通过这种设计能够实现需求。
但计算后发现,单表能存百亿条数据,而且索引设计比较合理,业务逻辑相对简单,无高并发请求,单表好像也可以搞。
总结
正常情况下,我们一般需要做水平分库分表,这就涉及到分布式事务,一定要考虑清楚是否能满足自己的需求、想用的SQL语句是否都能支持,考虑一下是否还有别的方案。
关于中间件的实现原理,了解的不是很深,后面有时间的话,可以学习一下。
资料
- MySQL 分库分表方案,总结的非常好!
- MySQL之分库分表(MyCAT实现)
- Mysql分库分表实战(一)——一文搞懂Mysql数据库分库分表
- MySql分表、分库、分片和分区知识
- 数据库分片(Sharding)与分区(Partition)的区别(转)
- 数据库分库分表中间件对比(很全)
- 分库分表中间件
- 分库分表:中间件方案对比
- XA 分布式事务原理
最后
大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)
我的个人博客为:https://shidawuhen.github.io/
往期文章回顾:
招聘
- 字节跳动|内推大放送
- 字节跳动|今日头条广州服务端研发工程师内推
- 字节跳动|抖音电商急招上海前端开发工程
- 字节跳动|抖音电商上海资深服务端开发工程师-交易
- 字节跳动|抖音电商武汉服务端(高级)开发工程师
- 字节跳动|飞书大客户产品经理内推咯
- 字节跳动|抖音电商服务端技术岗位虚位以待
- 字节跳动招聘专题
设计模式
- Go设计模式(15)-门面模式
- Go设计模式(14)-适配器模式
- Go设计模式(13)-装饰器模式
- Go设计模式(12)-桥接模式
- Go设计模式(11)-代理模式
- Go设计模式(10)-原型模式
- Go设计模式(9)-建造者模式
- Go设计模式(8)-抽象工厂
- Go设计模式(7)-工厂模式
- Go设计模式(6)-单例模式
- Go设计模式(5)-类图符号表示法
- Go设计模式(4)-代码编写优化
- Go设计模式(4)-代码编写
- Go设计模式(3)-设计原则
- Go设计模式(2)-面向对象分析与设计
- Go设计模式(1)-语法
语言
- 再也不怕获取不到Gin请求数据了
- 一文搞懂pprof
- Go工具之generate
- Go单例实现方案
- Go通道实现原理
- Go定时器实现原理
- Beego框架使用
- Golang源码BUG追查
- Gin框架简洁版
- Gin源码剖析
架构
存储
网络
工具
读书笔记
思考