数据库专题《三》——分库分表
这篇文章的脉络如下:
1、分库分表之前出现的问题
2、怎么分库分表?
3、分库分表的规则是什么?
下面按照这个脉络给出今天的文章。
一、单库单表存在的问题
假设你要设计一个电商网站,在一开始,User表、Order表、Product表等等各种表都在同一个数据库中,每个表都包含了大量的字段。在用户量比较少,访问量也比较少的时候,单库单表不存在问题。
但是公司可能发展的比较好,用户量开始大量增加,业务也越来越繁杂。一张表的字段可能有几十个甚至上百个,而且一张表存储的数据还很多,高达几千万数据,更难受的是这样的表还挺多。于是一个数据库的压力就太大了,一张表的压力也比较大。试想一下,我们在一张几千万数据的表中查询数据,压力本来就大,如果这张表还需要关联查询,那时间等等各个方面的压力就更大了。
(1)单库太大:数据库里面的表太多,所在服务器磁盘空间装不下,IO次数多CPU忙不过来。
(2)单表太大:一张表的字段太多,数据太多。查询起来困难。
此时就开始考虑如何解决问题了。
1、IO瓶颈
第一种:磁盘读IO瓶颈,热点数据太多,数据库缓存放不下,每次查询时会产生大量的IO,降低查询速度 -> 分库和垂直分表。
第二种:网络IO瓶颈,请求的数据太多,网络带宽不够 -> 分库。
2、CPU瓶颈
第一种:SQL问题,如SQL中包含join,group by,order by,非索引字段条件查询等,增加CPU运算的操作 -> SQL优化,建立合适的索引,在业务Service层进行业务计算。
第二种:单表数据量太大,查询时扫描的行太多,SQL效率低,CPU率先出现瓶颈 -> 水平分表。
二、主从复制架构
单库单表下越来越不满足需求,此时我们先考虑进行读写分离。我们将数据库的写操作和读操作进行分离, 使用多个从库副本(Slaver)负责读,使用主库(Master)负责写, 从库从主库同步更新数据,保持数据一致。
这在一定程度上可以解决问题,但是用户超级多的时候,比如几个亿用户,此时写操作会越来越多,一个主库(Master)不能满足要求了,那就把主库拆分,这时候为了保证数据的一致性就要开始进行同步,此时会带来一系列问题:
(1)写操作拓展起来比较困难,因为要保证多个主库的数据一致性。
(2)复制延时:意思是同步带来的时间消耗。
(3)锁表率上升:读写分离,命中率少,锁表的概率提升。
(4)表变大,缓存率下降:此时缓存率一旦下降,带来的就是时间上的消耗。
注意,此时主从复制还是单库单表,只不过复制了很多份并进行同步。
主从复制架构随着用户量的增加、访问量的增加、数据量的增加依然会带来大量的问题,那就要考虑换一种解决思路。就是今天所讲的主题,分库分表。
三、分库分表
不管是分库还是分表,都有两种切分方式:水平切分和垂直切分。下面我们分别看看如何切分。
3.1.水平分库
水平分库理论上切分起来是比较麻烦的,它是指将单张表的数据切分到多个服务器上去,每个服务器具有相应的库与表,只是表中数据集合不同。水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈。以表的主键字段为依据,按照一定策略(取模,hash等),将本来在一个库中的数据拆分到多个库中。
-
每个库的表结构都一模一样;
-
每个库的数据都不一样,没有任何交集;
-
所有库的表的数据的并集才是全量数据;
场景:系统绝对并发量上来了,分表难以根本上解决问题,并且还没有明显的业务归属来垂直分库。
分析:库多了,io和cpu的压力自然可以成倍缓解。
3.2.水平分表
单表的数据量太大。按照某种规则(RANGE,HASH取模等),将本来在一个表中的数据拆分到多个表中。但是这些表还是在同一个库中,所以库级别的数据库操作还是有IO瓶颈。这种情况是不建议使用的,因为数据量是逐渐增加的,当数据量增加到一定的程度还需要再进行切分。
-
拆分的表的结构都一样;
-
拆分的表数据都不一样,没有交集;
-
拆分的所有表的所有数据并集才是全量数据;
场景:系统绝对并发量并没有上来,只是单表的数据量太多,影响了SQL效率,加重了CPU负担,以至于成为瓶颈。推荐:一次SQL查询优化原理分析
分析:表的数据量少了,单次SQL执行效率高,自然减轻了CPU的负担。
3.3.垂直分库
一个数据库的表太多。此时就会按照一定业务逻辑进行垂直切,将不同业务的表拆分到不同的库中,比如用户相关的表放在一个数据库里,订单相关的表放在一个数据库里。注意此时不同的数据库应该存放在不同的服务器上,此时磁盘空间、内存、TPS等等都会得到解决。
-
每个库的结构都不一样;
-
每个库的数据也不一样,没有交集;
-
所有库的数据的并集才是全量数据;
场景:系统绝对并发量上来了,并且可以抽象出单独的业务模块。
分析:到这一步,基本上就可以服务化了。例如,随着业务的发展一些公用的配置表、字典表等越来越多,这时可以将这些表拆到单独的库中,甚至可以服务化。再有,随着业务的发展孵化出了一套业务模式,这时可以将相关的表拆到单独的库中,甚至可以服务化。
3.4.垂直分表
表中的字段较多,一般将不常用的、 数据较大、长度较长的拆分到“扩展表“。一般情况加表的字段可能有几百列,此时是按照字段进行数竖直切。注意垂直分是列多的情况。以字段为依据,按照字段的使用情况(常用和不常用),将表中字段拆到不同的表,主要分为主表和扩展表。
- 每个表的结构都不一样;
- 每个表的数据也不一样,一般来说,主表和扩展表的字段至少有一列交集,一般是主键,用于主表和扩展表关联;
- 所有表的数据的并集是全量数据;
场景:系统绝对并发量并没有上来,表的记录并不多,但是字段多,并且热点数据和非热点数据在一起,单行数据所需的存储空间较大。以至于数据库缓存的数据行减少,查询时会去读磁盘数据产生大量的随机读IO,产生IO瓶颈。
分析:可以用列表页和详情页来帮助理解。垂直分表的拆分原则是将热点数据(可能会冗余经常一起查询的数据)放在一起作为主表,非热点数据放在一起作为扩展表。这样更多的热点数据就能被缓存下来,进而减少了随机读IO。拆了之后,要想获得全部数据就需要关联两个表来取数据。
但记住,千万别用join,因为join不仅会增加CPU负担并且会讲两个表耦合在一起(必须在一个数据库实例上)。关联数据,应该在业务Service层做文章,分别获取主表和扩展表数据然后用关联字段关联得到全部数据。
四、分库分表步骤
根据容量(当前容量和增长量)评估分库或分表个数 -> 选key(均匀)-> 分表规则(hash或range等)-> 执行(一般双写)-> 扩容问题(尽量减少数据的移动)。
五、分库分表工具
- sharding-sphere:jar,前身是sharding-jdbc;
- TDDL:jar,Taobao Distribute Data Layer;
- Mycat:中间件。
六、分库分表问题
1、非partition key的查询问题
基于水平分库分表,拆分策略为常用的hash法。
端上除了partition key只有一个非partition key作为条件查询
映射法
基因法
注:写入时,基因法生成user_id,如图。关于xbit基因,例如要分8张表,23=8,故x取3,即3bit基因。根据user_id查询时可直接取模路由到对应的分库或分表。
根据user_name查询时,先通过user_name_code生成函数生成user_name_code再对其取模路由到对应的分库或分表。id生成常用snowflake算法。
端上除了partition key不止一个非partition key作为条件查询
映射法
冗余法
注:按照order_id或buyer_id查询时路由到db_o_buyer库中,按照seller_id查询时路由到db_o_seller库中。感觉有点本末倒置!有其他好的办法吗?改变技术栈呢?
后台除了partition key还有各种非partition key组合条件查询
NoSQL法
冗余法
2、非partition key跨库跨表分页查询问题
基于水平分库分表,拆分策略为常用的hash法。
注:用NoSQL法解决(ES等)。
3、扩容问题
基于水平分库分表,拆分策略为常用的hash法。
水平扩容库(升级从库法)
注:扩容是成倍的。
水平扩容表(双写迁移法)
- 第一步:(同步双写)修改应用配置和代码,加上双写,部署;
- 第二步:(同步双写)将老库中的老数据复制到新库中;
- 第三步:(同步双写)以老库为准校对新库中的老数据;
- 第四步:(同步双写)修改应用配置和代码,去掉双写,部署;
七、分库分表总结
- 分库分表,首先得知道瓶颈在哪里,然后才能合理地拆分(分库还是分表?水平还是垂直?分几个?)。且不可为了分库分表而拆分。
- 选key很重要,既要考虑到拆分均匀,也要考虑到非partition key的查询。
- 只要能满足需求,拆分规则越简单越好。
八、分库分表之后的问题
1、联合查询困难
联合查询不仅困难,而且可以说是不可能,因为两个相关联的表可能会分布在不同的数据库,不同的服务器中。
2、需要支持事务
分库分表后,就需要支持分布式事务了。数据库本身为我们提供了事务管理功能,但是分库分表之后就不适用了。如果我们自己编程协调事务,代码方面就又开始了麻烦。
3、跨库join困难
分库分表后表之间的关联操作将受到限制,我们无法join位于不同分库的表,也无法join分表粒度不同的表, 结果原本一次查询能够完成的业务,可能需要多次查询才能完成。我们可以使用全局表,所有库都拷贝一份。
4、结果合并麻烦
比如我们购买了商品,订单表可能进行了拆分等等,此时结果合并就比较困难。
---------------------------------------------------
作者:杨兮臣
本博客所有文章仅用于学习、研究和交流目的,欢迎非商业性质转载。
博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高,不足和错误之处在所难免,希望大家能够批评指出。
博主是利用闲暇时间,把自己毕生所学整理一下,感谢行业的技术大咖