前言
在当今互联网的发展下,很多软件拥有大量用户的使用。用户每天产生大量数据。这些数据我们会保存到关系型数据库中,而如何设计一个可以快速响应的并且能够容易容纳大量数据的数据库便显得很重要了。此篇讲述如何通过分库分表设计解决以上问题。 一切技术都是为了解决存在的问题。
数据拆分
数据量大,解决的办法就是进行数据拆分到不同的数据库。而数据切分的两种常见的方式就是垂直切分与水平切分。
垂直切分
垂直切分又称为纵向切分,根据业务的维度(模块),将原本的数据库中的表进行分类,分到不同的数据库。如下图
优点:
- 最直观的就是业务清晰。
- 简单,容易扩展
- 方便实现冷热数据设计
- 数据维护简单
缺点:
- 无法解决单数据库性能问题。
- 部分业务无法join(即关联),可以编写接口进行访问,但增加程序复杂度
- 事务管理复杂
水平切分
水平切分又称为横向切分。即就是把单个数据库的结构不改变。按照数据进行切分,每个数据库存放一部分数据,达到数据分服务器存储。如下图:
我们想一个场景,当一个系统有1500万用户使用,未切分时数据库承受1500万的请求压力,切分成10个,则每个数据库承受150万的压力。成倍减少了访问压力。
优点:
- 降低数据库访问压力,提高性能
- 表结构相同,只需要合适的路由规则
缺点:
- 数据分散,不易管理。
- 拆分规则需要一个好的设计
- 会出现事务一致性问题
- 分页,排序等难以解决
- 存在多数据源
拆分阶段
- 单库单表
这个阶段没什么讲的,比如用户表,则所有数据都存到该表。 - 单库多表
当数据量增大时,此时我们进行表的拆分。把用户表拆分多个用户表,用户表结构相同。比如user表拆分成user1表和user2表,则user1表和user2表合起来的数据就 是user表的所有数据。 - 多库多表
当数据库服务器存储数量过大,并且影响到查询效率时,此时应该把表存放到多个数据库。
如何操作拆分的数据库(客户端分片)
程序自定义规则实现
我们通过自己编写指定的规则来实现访问哪一个表或数据库。这种方式具有侵入性,但是简单。比如我们设计user拆分为4个表然后对id进行取模存储到不同数据库,这是保存到数据库,当我们进行读取时采用相同算法进行获取即可。但是有时候切分规则并不会这么简单。
通过ORM框架实现
比如使用mybatis进行查询的时候指定一个参数,这个参数是表的索引,例如user表分为user0,user1,0和1代表的索引值,查询的时候传递表索引参数指定操作的表。
<select>
select * from user#{index} where id = #{userID}
</select>
通过JDBC协议实现
定制特定的JDBC协议实现,优点就是开发人员不必关心分库分表的实现,对开发人员透明,无侵入性,流行的有sharding JDBC。
通过代理分片实现
代理分片,就是通过在数据和业务逻辑之间提供一个代理层,分片规则在代理层实现。代理层提供接口供开发人员使用。缺点,增加了代理层。对数据库的操作增加一层网络传输。影响性能。并且增加我们的维护成本,比如Mycat,我们要配置高可用方案保证系统稳定性。
问题
当我们使用分库分表这种设计虽然解决了我们的大问题,但其带来的问题也不小。当把一个数据集拆分成多份时,会打破数据完整性,这个事实上是反数据库设计的。
-
数据库的扩容和迁移操作变得更难。不小心就会导致数据丢失,重复数据问题。
-
数据库跨维度查询问题
我们想一个场景,当数据库1(db1)中user1购买商品A时,此时我们把购买记录保存到user1所在的db1库中。现在db2中的user2购买商品A,交易记录保存到db2中,即购买记录存到买家的维度上。我们现在如果查询商品A的购买记录。发现我们要遍历两个库才能查询所有的记录。反之交易记录存到商品的库。则用户查询记录要遍历所有的商品的库。
- 在用户和商品各自在的库都保存交易记录的信息
- 查询所有分片数据,合并。效率太低,不是太建议
- 搜索引擎实现 -
数据库事务问题
当进行跨库操作时,很难保证其事务性。这其实是个很大的问题,比如在金融方面的系统上,我们必须保证其操作的准确性和数据的完整性。则多处会使用事务。
结语
此篇只讲了一些理念,未涉及到实际操作,后面会出。