数据库水平切分策略 - 分库寻址
当数据库的数据量很大时,就需要对库或表进行水平切分。最常见的水平切分方式,共有四种策略:
- 索引表法
- 缓存映射法
- 计算法
- 基因法
一、数据模型
我们以 “用户中心” 数据库作为数据模型,讲解数据库的水平切分策略。
用户中心是一个常见业务,主要提供用户注册、登录、查询以及修改等服务,其核心元数据:
User(uid, uname, passwd, sex, age,nickname, …)。
- uid 为用户的ID,主键。
- uname, passwd, sex, age, nickname 为用户的属性。
用户中心是几乎每一个公司必备的基础服务,用户注册、登录、信息查询与修改都离不开用户中心。
二、数据库水平切分方案
当数据量越来越大时,需要多用户中心进行水平切分。最常见的水平切分方式,按照 uid 取模分库:
通过 uid 取模,将数据分布到多个数据库实例上去,提高服务实例个数,降低单库数据量,以达到扩容的目的。
水平切分之后:
uid 属性上的查询可以直接路由到库,如上图,假设访问 uid=124 的数据,取模后能够直接定位db-user1。
大多数场景下,我们都不会通过 uid 进行的登录,而是使用 uname 登录。那么对于 uname 上的查询,就不能这么幸运了:
uname 上的查询,如上图,假设访问 uname=aizws 的数据,由于不知道数据落在哪个库上,往往需要遍历所有库(扫全库法),当分库数量多起来,性能会显著降低。
用 uid 分库,如何高效实现 uname 上的查询,是本文将要讨论的问题。
三、数据库水平切分查询策略
1)方案一:索引表法
思路:uid 能直接定位到库,uname 不能直接定位到库,如果通过 uname 能查询到 uid,问题解决。
解决方案:
- (1)建立一个索引表记录 uname 到 uid 的映射关系;
- (2)用 uname 来访问时,先通过索引表查询到 uid,再定位相应的库;
- (3)索引表属性较少,可以容纳非常多数据,一般不需要分库;
- (4)如果数据量过大,可以通过 uname 来分库;
潜在不足:多一次数据库查询,性能下降一倍。
2)方案二:缓存映射法
思路:访问索引表性能较低,把映射关系放在缓存里性能更佳。
解决方案:
- (1)uname 查询先到 cache 中查询 uid,再根据 uid 定位数据库;
- (2)假设 cache miss,采用扫全库法获取 uname 对应的 uid,放入 cache;
- (3)uname 到 uid 的映射关系不会变化,映射关系一旦放入缓存,不会更改,无需淘汰,缓存命中率超高;
- (4)如果数据量过大,可以通过 name 进行 cache 水平切分;
潜在不足:多一次cache查询。
3)方案三:计算法,uname 生成 uid
思路:不进行远程查询,由 uname 直接得到 uid。
解决方案:
- (1)在用户注册时,设计函数 uname 生成 uid,uid=f(uname),按 uid 分库插入数据;
- (2)用 uname 来访问时,先通过函数计算出 uid,即 uid=f(uname)再来一遍,由 uid 路由到对应库;
潜在不足:该函数设计需要非常讲究技巧,有 uid 生成冲突风险。
4)方案四:基因法,uname 基因融入 uid
思路:不能用uname生成uid,可以从uname抽取“基因”,融入uid中。
假设分 8 库,采用 uid%8 路由,潜台词是,uid 的最后 3 个 bit 决定这条数据落在哪个库上,这 3 个 bit 就是所谓的“基因”。
解决方案:
- (1)在用户注册时,设计函数 uname 生成 3bit 基因,uname_gene=f(uname),如上图粉色部分;
- (2)同时,生成 61bit 的全局唯一 id,作为用户的标识,如上图绿色部分;
- (3)接着把 3bit 的 uname_gene 也作为 uid 的一部分,如上图屎黄色部分;
- (4)生成 64bit 的 uid,由 id 和 uname_gene 拼装而成,并按照 uid 分库插入数据;
- (5)用 uname 来访问时,先通过函数由 uname 再次复原 3bit 基因,uname_gene=f(uname),通过 uname_gene%8 直接定位到库。
下一章:MySQL分库分表设计
1. 为什么要分库分表物理服务机的CPU、内存、存储设备、连接数等资源有限,某个时段大量连接同时执行操作,会导致数据库在处理上遇到性能瓶颈。为了解决这个问题,行业先驱门充分发扬了分而治之的思想,对大库表进 ...