为什么要分库分表
作者:周磊 日期:2021.6.24
参考文章
使用场景
B+树的索引使得数据量增加到一定程度时,IO次数的增加导致系统性能下降。高并发对集中数据库造成不小压力(单表操作数据量有最优值,mysql为1000万左右)
提醒
不到万不得已不用轻易使用分库分表这个大招,避免"过度设计"和"过早优化"。
分库分表之前,不要为分而分,先尽力去做力所能及的事情,例如:升级硬件、升级网络、读写分离、索引优化等等。
定义
将数据分散地存放至多个数据库或者表中,提高性能。分库还可以有效的分散数据库单点的访问量。使用多主多重的分片方式,可以有效地避免数据单点,从而提升数据架构的可用性
-
拆分方式:
- 垂直分片:按照业务将表进行归类,分布到不同数据库中【但部分表关联无法在数据库级别完成,需要在程序中完成;单表的大数据量访问仍然会有压力;事务处理相对复杂,需要分布式事务】
例如,我们会建立定义数据库workDB、商品数据库payDB、用户数据库userDB、日志数据库logDB等,分别用于存储项目数据定义表、商品定义表、用户数据表、日志数据表等。
- 水平分片:而是通过某个字段,根据某种规则将数据分散至多个库或者表中,每个分片包含数据的一部分。例如:根据主键分片,偶数主键的记录放入 0 库(或表),奇数主键的记录放入 1 库(或表)【但拆分规则更复杂,后期数据的维护难度增加,人为手动定位数据困难】
当一个表中的数据量过大时,我们可以把该表的数据按照某种规则,例如userID散列,进行划分,然后存储到多个结构相同的表,和不同的库上。
-
划分依据
范围区分(range)
:按月\按区\按其他的等特殊的属性维度进行分片预定义范围
:预估有多少数据的容量,对数据进行范围的分配,0-100->A 101-200->B取模 Hash
:对指定的字段进行取模运算,匹配对应的库和表。
-
可能的问题:
- 原来的SQL不一定能正常运行
- 跨库事务也是分布式数据库集群遇到的问题【合理使用分表,可以在降低单表数据量的情况下,尽量使用本地事务,善于使用同库不同表可以有效避免分布式事务带来的麻烦】
- 跨库跨表的join问题
- ID问题
-
解决方案
-
ID问题:
- 分布式需要全局唯一ID(UID,Twitter的分布式自增ID算法Snowflake)
- 一旦数据库被切分到多个物理结点上,我们将不能再依赖数据库自身的主键生成机制。一方面,某个分区数据库自生成的ID无法保证在全局上是唯一的;另一方面,应用程序在插入数据之前需要先获得ID,以便进行SQL路由.
-
如果数据库是因为表太多而造成海量数据,并且项目的各项业务逻辑划分清晰、低耦合,那么规则简单明了、容易实施的垂直切分必是首选。
而如果数据库中的表并不多,但单表的数据量很大、或数据热度很高,这种情况之下就应该选择水平切分,水平切分比垂直切分要复杂一些,它将原本逻辑上属于一体的数据进行了物理分割,除了在分割时要对分割的粒度做好评估,考虑数据平均和负载平均,后期也将对项目人员及应用程序产生额外的数据管理负担。 -
针对跨库跨表join问题:
- 全局表。所谓全局表,就是有可能系统中所有模块都可能会依赖到的一些表。比较类似我们理解的“数据字典”。为了避免跨库join查询,我们可以将这类表在其他每个数据库中均保存一份。
- 字段冗余(反范式设计)
- 数据同步(不太懂),使用ETL工具
- 在系统层面,通过调用不同模块的组件或者服务,获取到数据并进行字段拼装。
- 简单字段组装的情况下,我们只需要先获取“主表”数据,然后再根据关联关系,调用其他模块的组件或服务来获取依赖的其他字段(如例中依赖的用户信息),最后将数据进行组装。通常,我们都会通过缓存来避免频繁RPC通信和数据库查询的开销。
- 解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据。
-
分库分表中间件(不太懂)
- Sharding-JDBC
-
事务问题:
- 使用分布式事务
- 由应用程序和数据库共同控制
-
跨节点的count,order by,group by以及聚合函数问题问题:
- 分别在各个节点上得到结果后在应用程序端进行合并。和join不同的是每个结点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。
-
分库策略:
- 一般初次分库建议分4-8个库。
- 避免数据库中的数据依赖另一数据库中的数据。
- 当Slave机器达到一定的数量就得考虑分库了。 当写压力很大的时候,就必须得进行分库操作。
-
分表策略:
- 大数据量并且访问频繁的表,将其分为若干个表
- 经测试在单表1000万条记录一下,写入读取性能是比较好的. 这样在留点buffer,那么单表全是数据字型的保持在
800万条记录以下, 有字符型的单表保持在500万以下.
-
分库还是分表:
-
分库分表对实时性的查询
- 通过搜索引擎解决,但如果实时性要求很高,又得关系到实时搜索。
-
分布问题:
- 避免数据库中的数据依赖另一数据库中的数据。
所有的写操作都必须对应到Master,读操作可以在 Master和Slave机器上进行,Slave与Master的结构完全一样,一个Master可以有多个Slave,甚至Slave下还可以挂 Slave,通过此方式可以有效的提高DB集群的 QPS.
所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
此外,可以看出Master是集群的瓶颈,当写操作过多,会严重影响到Master的稳定性,如果Master挂掉,整个集群都将不能正常工作。
-
题外话
- 数据库事务
- 定义:
- 表-粒度
- 范式设计(NF)
- OLAP和OLTP
- DB schema
- 轮询