一、为什么要分库分表
分库分表是一种数据库优化策略,主要用于解决大型应用或高并发场景下数据库性能瓶颈的问题。具体来说,分库分表可以带来以下好处:
- 提高性能:
- 减少单个数据库实例的负载,避免单点性能瓶颈。
- 当数据量增长到一定程度时,单一数据库的读写性能会受限于其硬件资源(如CPU、内存、磁盘I/O)。
- 分散数据到多个数据库或表中可以降低每个实例的负载,从而提高查询和事务处理速度。
- 增强可扩展性:
- 允许通过添加更多的数据库服务器来线性扩展存储和处理能力。
- 支持分布式部署,利用集群的优势,提高整体系统的吞吐量和响应速度。
- 优化资源利用:
- 单个数据库服务器的资源(如连接数)是有限的,分库分表可以更高效地利用这些资源。
- 避免大量数据导致的索引深度增加,减少磁盘I/O次数,提高查询效率。
- 提高可用性和可靠性:
- 分布式架构可以更好地实现故障隔离,如果某个数据库出现故障,其他数据库仍然可以正常工作。
- 提供数据冗余,增强数据安全性和灾难恢复能力。
- 简化管理与维护:
- 在某些情况下,分库分表可以使数据库管理更加简单,比如按业务模块划分数据库,便于管理和权限控制。
- 支持大数据分析:
- 大数据场景下,分库分表可以支持并行处理,加速数据分析和报表生成。
然而,分库分表也带来了新的挑战,例如:
- 复杂性增加:应用程序需要处理跨库和跨表的事务,这增加了开发和维护的难度。
- 数据一致性:确保跨库跨表数据的一致性变得更加困难,可能需要引入分布式事务或消息队列机制。
- 查询优化:复杂的查询可能需要在多个数据库之间进行,这要求更高的查询优化技巧和工具支持。
二、如何分库分表
分库分表的核心理念就是对数据进行切分(Sharding
),以及切分后如何对数据的快速定位与查询结果整合。而分库与分表都可以从:垂直
(纵向)和 水平
(横向)两种纬度进行切分。
垂直切分
1、垂直分库
垂直分库核心理念:专库专用
2、垂直分表
垂直分表
是基于数据表的列(字段)为依据切分的,是一种大表拆小表的模式。
例如:一张 order
订单表,将订单金额、订单编号等访问频繁的字段,单独拆成一张表,把 blob
类型这样的大字段或访问不频繁的字段,拆分出来创建一个单独的扩展表 work_extend
,这样每张表只存储原表的一部分字段,再将拆分出来的表分散到不同的库中。
水平切分
1、水平分库
水平分库是把同一个表按一定规则拆分到不同的数据库中,每个库可以位于不同的服务器上,以此实现水平扩展,是一种常见的提升数据库性能的方式。
这种方案往往能解决单库存储量及性能瓶颈问题,但由于同一个表被分配在不同的数据库中,数据的访问需要额外的路由工作,因此系统的复杂度也被提升了。
例如下图,订单DB_1
、订单DB_2
、订单DB_3
三个数据库内有完全相同的表 order
,我们在访问某一笔订单时可以通过对订单的订单编号取模的方式 订单编号 mod 3 (数据库实例数)
,指定该订单应该在哪个数据库中操作。
2、水平分表
水平分表是在同一个数据库内
,把一张大数据量的表按一定规则,切分成多个结构完全相同表,而每个表只存原表的一部分数据。
例如:一张 order
订单表有 900万数据,经过水平拆分出来三个表,order_1
、order_2
、order_3
,每张表存有数据 300万,以此类推。
水平分表尽管拆分了表,但子表都还是在同一个数据库实例中,只是解决了单一表数据量过大的问题,并没有将拆分后的表分散到不同的机器上,还在竞争同一个物理机的CPU、内存、网络IO等。要想进一步提升性能,就需要将拆分后的表分散到不同的数据库中,达到分布式的效果,这就是我们常说的 分库分表
三、分库分表路由算法
常见的有 取模算法
和 范围限定算法
1、取模算法
按字段取模(对hash结果取余数 (hash() mod N),N为数据库实例数或子表数量)是最为常见的一种切分方式。
还拿 order
订单表举例,先对数据库从 0 到 N-1
进行编号,对 order
订单表中 work_no
订单编号字段进行取模,得到余数 i,i=0存第一个库,i=1存第二个库,i=2存第三个库…以此类推。
这样同一笔订单的数据都会存在同一个库、表里,查询时用相同的规则,用 work_no
订单编号作为查询条件,就能快速的定位到数据。
优点:
数据分片相对比较均匀,不易出现请求都打到一个库上的情况。
缺点:
这种算法存在一些问题,当某一台机器宕机,本应该落在该数据库的请求就无法得到正确的处理,这时宕掉的实例会被踢出集群,此时算法变成hash(userId) mod N-1,用户信息可能就不再在同一个库中了。
2、范围限定算法
按照 时间区间
或 ID区间
来切分,比如:我们切分的是用户表,可以定义每个库的 User
表里只存10000条数据,第一个库只存 userId
从1 ~ 9999的数据,第二个库存 userId
为10000 ~ 20000,第三个库存 userId
为 20001~ 30000…以此类推,按时间范围也是同理。
优点:
- 单表数据量是可控的
- 水平扩展简单只需增加节点即可,无需对其他分片的数据进行迁移
- 能快速定位要查询的数据在哪个库
缺点:
- 由于连续分片可能存在数据热点,比如按时间字段分片,可能某一段时间内订单骤增,可能会被频繁的读写,而有些分片存储的历史数据,则很少被查询。
四、分库分表的难点
1、分布式事务
由于表分布在不同库中,不可避免会带来跨库事务问题。一般可使用 "三阶段提交"
和 "两阶段提交"
处理,但是这种方式性能较差,代码开发量也比较大。通常做法是做到最终一致性
的方案,如果不苛求系统的实时一致性,只要在允许的时间段内达到最终一致性即可,采用事务补偿的方式。
2、分页、排序、跨库联合查询
分页、排序、联合查询是开发中使用频率非常高的功能,但在分库分表后,这些看似普通的操作却是让人非常头疼的问题。将分散在不同库中表的数据查询出来,再将所有结果进行汇总整理后提供给用户。
3、分布式主键
分库分表后数据库的自增主键意义就不大了,因为我们不能依靠单个数据库实例上的自增主键来实现不同数据库之间的全局唯一主键,此时一个能够生成全局唯一ID的系统是非常必要的,那么这个全局唯一ID就叫 分布式ID
。
4、读写分离
不难发现大部分主流的关系型数据库都提供了主从架构的高可用方案,而我们需要实现 读写分离
+ 分库分表
,读库与写库都要做分库分表处理。
5、数据脱敏
数据脱敏,是指对某些敏感信息通过脱敏规则进行数据转换,从而实现敏感隐私数据的可靠保护,如身份证号、手机号、卡号、账号密码等个人信息,一般这些都需要进行做脱敏处理。
后续博客将介绍分库分表的具体实现,以及分库分表以后遇到的坑。