之前一篇文章已经谈到了数据库集群之主从集群也就是读写分离, 也提到了读写分离其实只是分担了访问的压力, 但是存储的压力没有解决.
存储的压力说白了就是随着系统的演化, 需求的增加, 可能表的数量会逐渐增多, 比如一段时间上个新功能就得加个表. 并且随着用户量的增多类似用户表的行数肯定会增多, 订单表的数据肯定会随着时间而增多, 当这种数据量达到千万甚至上亿的时候, 读写分离就已经满足不了, 读写性能下降严重.
也就是一台服务器的资源例如 CPU, 内存, IO, 磁盘等是有限的, 所以这时候分库分表就上啦!
分库
分库讲白了就是比如现在你有一个数据库服务器, 数据库中有两张表分别是用户表和订单表. 如果要分库的话现在你需要买两台机子, 搞两个数据库分别放在两台机子上, 并且一个数据库放用户表, 一个数据库放订单表
这样存储压力就分担到两个服务器上了, 但是会带来新的问题, 所以东西变复杂了都会有新的问题产生.
1, 联表查询问题 也就是 join 了, 之前在一个数据库里面可以用上 join 用一条 sql 语句就可以联表查询得到想要的结果, 但是现在分为多个数据库了, 所以 join 用不上了. 就比如现在要查注册时间在 2019 年之后用户的订单信息, 你就需要先去数据库 A 中用户表查询注册在 2019 年之后的信息, 然后得到用户 id, 再拿这些 id 去数据库 B 订单表中查找订单信息, 然后再拼接这些信息返回. 所以等于得多写一些代码了.
2, 事务问题 搞数据库基本上都离不开事务, 但是现在不同的数据库事务就不是以前那个简单的本地事务了, 而是分布式事务了, 而引入分布式事务也提高了系统的复杂性, 并且有些效率不高还会影响性能例如 MySQL XA. 还有基于消息中间件实现分布式事务的等等这里不展开讲述.
分表
我们已经做了分库了, 但是现在情况是我们的表里面的数据太多了, 就一不小心你的公司的产品火了, 像抖音这种, 所有用户如果就存在一张表里吃不消, 所以这时候得分表. 分别又分垂直分表和水平分表.
垂直分表
垂直分表的意思形象点就像坐标轴的 y 轴, 把 x 轴切成了两半, 对应到我们的表就是比如我们表有 10 列, 现在一刀切下去, 分成了两张表, 其中一张表 3 列, 另一张表 7 列.
这个一刀切下去让两个表分别有几列不是固定的, 垂直分表适合表中存在不常用并且占用了大量空间的表拆分出去.
就拿头条的用户信息, 比如用户表只有用户 id, 昵称, 手机号, 个人简介这 4 个字段. 但是手机号和个人简介这种信息就属于不太常用的, 占用的空间也不小, 个人简介有些人写了一坨. 所以就把手机号和个人简介这两列拆分出去.
那垂直分表影响就是之前只要一个查询的, 现在需要两次查询才能拿到分表之前的完整用户表信息.
水平分表
水平分表的意思形象点就像坐标轴的 x 轴, 把 y 轴切成了两半 (当然不仅限于切一刀, 可以切好几份). 也拿用户表来说比如现在用户表有 5000 万行数据, 我们切 5 刀, 分成 5 个表, 每个表 1000 万行数据.
水平分表就适合用户表行数很多的情况下, 一般单表行数超过 5000 万就得分表, 如果单表的数据比较复杂那可能 2000 万甚至 1000 万就得分了, 这个得看实际情况有些表很简单可能一亿行都不用分. 所以当一个表行数超过千万级别的时候关注一下, 如果没有性能问题就可以再等等看, 不要急着分表, 因为分表会是带来很多问题.
水平分表的问题比垂直分表就更烦了.
要考虑怎么切, 讲的高级点就叫路由
1, 按 id 也就是范围路由, 比如 id 值 1999 万的放一张表, 1000 万 1999 放一张表, 一次类推. 这个得试的, 因为范围分的大了, 可能性能还有问题, 范围分的小了.. 那表不得多死.
这种分法的好处就是容易切啊, 简单粗暴, 以后新增的数据分表都不会影响到之前的数据, 之前的数据都不需要移动.
2, 哈希路由 就是取几列哈希一下看看数据哪个库, 比如拿 id 来做哈希, 1500 取余 8 等于 4, 所以这条记录就放在 user_4 这个表中, 2011 取余 8 等于 3, 所以这条记录就放在 user_3 中. 这种分法好处就是分的很均匀, 基本上每个表的数据都差不多, 但是以后新增数据又得分表了咋办, 以前的数据都得动, 比较烦!
3, 搞一张表来存储路由关系 还是拿用户表来说, 就是弄一个路由表, 里面存 userId 和表编号, 表示这个 userId 是这张 user 表的的. 这种方式也简单, 之后又要分表了之后改改路由表, 迁移一部分数据. 但是这种方法导致每次查询都得查两次, 并且如果路由表太大了, 那路由表又成为瓶颈了!
再说说查询时候的问题.
比如你要查注册时间最早的前 100 名用户, 这就等于你得在水平分的每一张表都 order by 一下注册时间并且取 100 个, 然后再把每个表的 100 个结果对比一下得到最终的结果. 首先操作变麻烦了, 以前一个 order by 就搞定的事情现在变的复杂了, 而且还得考虑一个因素就是时间的问题, 如果你拆成了 20 个表, 那你得执行 20 个 order by, 如果是串行执行的话, 这个时间开销也是个问题!
分库分表的实现
具体实现也分为程序代码封装, 数据库中间件封装. 实现难度会比读写分离更大, 至于两种封装的比较在讲读写分离时候已经说了, 这里不再赘述.
总结
说了这么多好像分库分表一点都不好啊, 没错会引入很多问题, 所以在架构设计要遵循演化原则, 任何东西都不是一蹴而就的, 在不同场景适配不同的架构, 架构只有合适的, 没有一个架构可以适配任何场景.
在软件中简单够用就是好的, 技术没有贵贱, 不是用了分布式就牛逼, 越复杂的系统维护的成本和难度越高, 出现问题的几率越大. 这种架构的演化往往都是被用户所驱动的, 可以说是 "不得已而为之".
基本上单机数据库可以支撑 10 万用户量级别. 所以一般情况下像数据库吃不消就升级硬件, 优化数据库配置, 优化代码, 引入 Redis 等. 只有在真的不行了才上这些更复杂的东西.
来源: http://www.jianshu.com/p/0301d34a325b