Loading... # [数据库架构设计](http://www.codebaoku.com/arch-db/arch-db-index.html) ## 数据库架构分类 ### 介绍 介绍常见的 **四种** 数据库架构设计模型: **单库架构、分组架构、分片架构和分组分片架构** ,以及每种架构的 **使用场景、存在的问题和对应的解决方案** 。 ### 一、数据模型 我们以 “ **用户中心** ” 数据库作为 **数据模型** ,讲解数据库架构设计的方法。 用户中心是一个 **常见业务,主要提供用户注册、登录、查询以及修改等服务,其核心元数据** : > User(uid, uname, passwd, sex, age,nickname, …)。 > uid 为用户的ID,主键。 > uname, passwd, sex, age, nickname 为用户的属性。 ### 二、单库架构 ![单库(数据库单库架构)](http://flt-pan.58heshihu.com/blog/typecho/lkjg56pk.png) 通常在 **业务初期** ,数据库无需特别的设计, **单库单表就能满足业务需求** ,这也是 **最常见的数据库架构** 。 > user-service:用户中心服务,为调用者提供 RPC 接口或者 Restful API。 > user-db:使用一个库进行数据存储,提供数据库读写服务。 ### 三、分组架构 ![分组(数据库分组架构)](http://flt-pan.58heshihu.com/blog/typecho/lkjg5dy7.png) #### 1. 什么是分组架构 分组架构是最常见的 **一主多从,主从同步,读写分离** 的数据库架构。 > user-service:用户中心服务。 > user-db-M(master):主库,提供数据库写服务。 > user-db-S(slave):从库,提供数据库读服务。 > 主库和从库构成的数据库集群称为“组”。 #### 2. 分组架构有什么特点 同一个组里的数据库集群: > 主从之间通过 **binlog** 进行数据同步。 > 多个范例数据库结构完全相同。 > 多个范例存储的数据完全相同, **本质上是将数据进行复制** 。 #### 3. 分组架构解决什么问题 **大部分互联网业务读多写少,数据库的读往往最先成为性能瓶颈** 。如果希望: > 线性提升数据库读性能。 > 通过消除读写锁冲突提升数据库写性能。 > 通过冗余从库实现数据的“读高可用”。 此时可以使用分组架构。但是, **在分组架构中,数据库的主库依然是写单点** 。 总之, **分组架构是为了解决 “读写并发量高” 的问题,而实施的架构设计。** ### 四、分片架构 ![分片(数据库分片架构)](http://flt-pan.58heshihu.com/blog/typecho/lkjgj2us.png) #### 1. 什么是分片架构 分片架构是 **水平切分(sharding)** 数据库架构。 > user-service:用户中心服务。 > user-db1:水平切分成2份中的第一份。 > user-db2:水平切分成2份中的第二份。 **分片后,多个数据库范例也会构成一个数据库集群** 。 #### 2. 水平切分,应该分库还是分表 **强烈建议分库,而不是分表** ,因为: > 分表依然共用一个数据库文件,仍然有磁盘 IO 的竞争。 > 分库能够将数据迁移到不同数据库范例,甚至数据库服务器,扩展性更好。 #### 3. 水平切分,用什么算法 常见的水平切分算法 **有 “范围法” 和 “哈希法”** : ##### 范围法 ![范围分片(数据库水平切分的范围法)](http://flt-pan.58heshihu.com/blog/typecho/lkjgonhz.png) 以用户中心的业务主键 **uid** 为 **划分依据** , **将数据水平切分到两个数据库范例上去** : > user-db1:存储0到1千万的 uid 数据。 > user-db2:存储0到2千万的 uid 数据。 ##### 哈希法 ![哈希分片(数据库水平切分的哈希法)](http://flt-pan.58heshihu.com/blog/typecho/lkjgto1g.png) 以用户中心的业务主键 **uid** 为 **划分依据** , **将数据水平切分到两个数据库范例上去** : > user-db1:存储 uid 取模为1的 uid 数据。 > user-db2:存储 uid 取模为0的 uid 数据。 这两种方法在互联网都有使用,其中 **哈希法使用较为广泛** 。 #### 4. 分片架构有什么特点 **同一个分片里的数据库集群:** > 多个范例之间本身不直接产生联系, **不像主从间有 binlog 同步** 。 > 多个范例的数据库结构完全相同。 > 多个范例存储的数据之间没有交集, **所有范例的数据并集构成全局数据** 。 #### 5. 分片架构解决什么问题 大部分互联网 **业务的数据量很大** , **单库** 容量容易 **成为瓶颈** ,此时 **通过分片可以** : > 线性提升数据库写性能。 > 线性提升数据库读性能。 > 降低单库数据容量。 总之, **分片架构是为了解决 “数据量大” 的问题,而实施的架构设计。** ### 五、分组+分片架构 ![分组+分片(数据库分组+分片架构)](http://flt-pan.58heshihu.com/blog/typecho/lkjh3ntn.png) **如果业务读写并发量很高,数据量也很大,通常需要实施分组+分片的数据库架构** : > 通过分片来降低单库的数据量,线性提升数据库的写性能。 > 通过分组来线性提升数据库的读性能,保证读库的高可用。 ### 六、数据库垂直切分 除了水平切分, **垂直切分也是一类常见的数据库架构设计** ,垂直切分 **一般和业务结合比较紧密。** ![垂直切分](http://flt-pan.58heshihu.com/blog/typecho/lkjh74xf.png) 以 **用户中心** 为例,可以这么 **进行垂直切分** : ```mysql User(uid, uname, passwd, sex, age, …) User_EX(uid, intro, sign, …) ``` > 垂直切分开的表,主键都是 uid。 > 登录名,密码,性别,年龄等属性放在一个垂直表(库)里。 > 自我介绍,个人签名等属性放在另一个垂直表(库)里。 #### 1. 如何进行垂直切分 根据业务情况对数据进行垂直切分时,一般 **要考虑属性的“长度”和“访问频度”两个因素** : > 长度较短,访问频率较高的放在一起。 > 长度较长,访问频度较低的放在一起。 **这是因为,数据库会以行(row)为单位,将数据加载到内存。在内存容量有限的情况下,长度短且访问频度高的属性,内存可以加载更多的数据,命中率提高,磁盘IO减少,数据库的读性能得到提升。** #### 2. 垂直切分有什么特点 垂直切分 **和水平切有相似的地方,又不太相同** : > 多个范例之间也不直接产生联系,即 **没有 binlog 同步** 。 > 多个范例 **数据库结构都不相同** 。 > 多个范例存储的数据之间 **至少有一列交集,一般来说是业务主键,所有范例间数据并集构成全局数据** 。 #### 3. 垂直切分解决什么问题 垂直切分即 **可以降低单库的数据量,还可以降低磁盘IO从而提升吞吐量,但它与业务结合比较紧密,并不是所有业务都能够进行垂直切分的。** ## 数据库架构设计 ### 介绍 数据库架构设计是 **针对海量数据的数据库,通过数据结构、存储形式和部署方式等方面的规划和设计,以解决数据库服务的高并发、高可用、一致性、可扩展以及性能优化等问题。** ### 一、可用性设计 可用性是 **指在某个考察时间,系统能够正常运行的概率或时间占有率的期望值。** 通常, **我们都要求某个系统具备“高可用性”** 。 所谓 **“高可用性”(High Availability)** 是指系统经过专门的设计, **从而减少停工时间,保持其服务的高度可用。** 数据库的高可用, **通常采用的解决方式为:复制+冗余。** #### 1. 保证 “读” 高可用的方法 数据库主从复制,冗余数据。如图所示: ![数据库主从复制,冗余数据](http://flt-pan.58heshihu.com/blog/typecho/lkjiodfi.png) **主库用于写数据,从库用于读数据** 。在 **一主多从** 的数据库架构中, **多份从库数据保证了读数据高可用** 。 数据库主从复制 **可能带来的问题:主从数据不一致。** #### 2. 保证 “写” 高可用的方法 双主模式,即复制主库,冗余数据。如图所示: ![双主模式,复制主库,冗余数据](http://flt-pan.58heshihu.com/blog/typecho/lkjiruyq.png) 很多公司 **采用单主模式,这是无法保证数据库写的高可用性。** 数据库双主模式 **可能带来的问题:双主同步 key 冲突,引起数据不一致。** **解决方案:** > a)方案一:由数据库或者业务层保证 key 在两个主库上不冲突。 > b)方案二:“双主” 当 “主从” 用,不做读写分离,当主库挂掉时,启用从库。如图下图: ![不做读写分离,当主库挂掉时,启用从库](http://flt-pan.58heshihu.com/blog/typecho/lkjiu8r8.png) **优点:读写都到主,解决了一致性问题;“双主”当“主从”用,解决了可用性问题** 带来的问题:读性能如何扩充?解决方案见下文 ### 二、读性能设计:如何扩展读性能 #### 1. 建立索引 **建立太多的索引,会带来以下问题** : > a)降低了写性能。 > b)索引占用内存多了,内存存放的数据就会减少,数据命中率降低,IO次数随之增加。 对于索引过多的问题,有以下 **解决方案** : > 不同的库可以建立不同索引。 > 主库只提供写,不建立索引。 ![建立索引](http://flt-pan.58heshihu.com/blog/typecho/lkjj051j.png) online 从库只提供 online 读,建立 online 读索引。 offline 从库只提供 offline 读,建立 offline 读索引。 #### 2. 增加从库 增加从库 **会引发主从不一致问题,从库越多,主从时延越长,不一致问题越严重** 。这种方案很常见,但我们在生产环境中没有采用。 ![增加从库](http://flt-pan.58heshihu.com/blog/typecho/lkjj2pue.png) #### 3. 增加缓存 **传统缓存的使用方案** : > a)发生写请求时,先淘汰缓存,再写数据库。 > b)发生读请求时,先读缓存,hit则返回,miss则读数据库并将数据入缓存(此时可能旧数据入缓存)。 ![增加缓存](http://flt-pan.58heshihu.com/blog/typecho/lkjj3fkk.png) 增加缓存 **会带来的两个问题** : > a)数据复制会引发一致性问题,由于主从延时的存在,可能引发缓存与数据库数据不一致。 > b)业务层要关注缓存,无法屏蔽“主+从+缓存”的复杂性。 我们**缓存的使用方案:服务+数据+缓存。** ![服务+数据+缓存](http://flt-pan.58heshihu.com/blog/typecho/lkjj5rpx.png) **这种方案带来的好处:** > a)引入服务层屏蔽“数据库+缓存” > b)不做读写分离,读写都到主的模式,不会引发不一致 ### 三、一致性设计 #### 1. 主从不一致的解决方案 a)方案一:引入中间件 ![引入中间件](http://flt-pan.58heshihu.com/blog/typecho/lkjjalb6.png) 中间件将key上的写路由到主,在一定时间范围内(主从同步完成的经验时间),该key上的读也路由到主库。 b)方案二:读写都到主 ![读写都到主](http://flt-pan.58heshihu.com/blog/typecho/lkjjaytz.png) 我们采用的方案, **不做读写分离,数据不会不一致。** #### 2. 数据库与缓存不一致的解决方案 两次淘汰法: ![两次淘汰法](http://flt-pan.58heshihu.com/blog/typecho/lkjje921.png) **异常的读写时序,或导致旧数据入缓存,一次淘汰不够,要进行二次淘汰。** > a)发生写请求时,先淘汰缓存,再写数据库,额外增加一个timer,一定时间(主从同步完成的经验时间)后再次淘汰。 > b)发生读请求时,先读缓存,hit则返回,miss则读数据库并将数据入缓存(此时可能旧数据入缓存,但会被二次淘汰淘汰掉,最终不会引发不一致)。 四、扩展性设计 [https://blog.58heshihu.com/index.php/archives/258/](https://blog.58heshihu.com/index.php/archives/258/) 或者 [https://www.cnblogs.com/wintersun/p/4638176.html](https://www.cnblogs.com/wintersun/p/4638176.html) ## 数据库水平切分策略 ### 分库寻址 **当数据库的数据量很大时,就需要对库或表进行水平切分** 。最常见的水平切分方式, **共有四种策略** : > 索引表法 > 缓存映射法 > 计算法 > 基因法 ### 一、数据模型 **我们以 “用户中心” 数据库作为数据模型,讲解数据库的水平切分策略。** 用户中心是一个常见业务,主要提供用户注册、登录、查询以及修改等服务,其核心元数据: > User(uid, uname, passwd, sex, age,nickname, …)。 > uid 为用户的ID,主键。 > uname, passwd, sex, age, nickname 为用户的属性。 **用户中心是几乎每一个公司必备的基础服务,用户注册、登录、信息查询与修改都离不开用户中心。** ### 二、数据库水平切分方案 **当数据量越来越大时,需要多用户中心进行水平切分** 。 **最常见** 的水平切分方式, **按照 uid 取模分库** : ![按照 uid 取模分库](http://flt-pan.58heshihu.com/blog/typecho/lkjjpzla.png) 通过 uid 取模, **将数据分布到多个数据库实例上去,提高服务实例个数,降低单库数据量,以达到扩容的目的。** **水平切分之后** : ![水平切分之后](http://flt-pan.58heshihu.com/blog/typecho/lkjjs11t.png) **uid 属性** 上的查询可以 **直接路由到库** ,如上图,假设访问 uid=124 的数据,取模后能够直接定位db-user1。 大多数场景下,我们都不会通过 uid 进行的登录,而是使用 uname 登录。那么 **对于 uname 上的查询** ,就不能这么幸运了: ![uname 访问,遍历库](http://flt-pan.58heshihu.com/blog/typecho/lkjjtefn.png) uname 上的查询,如上图,假设访问 uname=codebaoku 的数据, **由于不知道数据落在哪个库上,往往需要遍历所有库(扫全库法),当分库数量多起来,性能会显著降低** 。 **用 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中。** ![lkjk5zro.png](http://flt-pan.58heshihu.com/blog/typecho/lkjk5zro.png) **假设分 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、内存、存储设备、连接数等资源有限** ,某个时段 **大量连接同时执行操作** ,会导致数据库在处理上遇到 **性能瓶颈** 。 为了解决这个问题,行业先驱门充分发扬了 **分而治之** 的思想,对大库表进行分割, 然后实施更好的控制和管理,同时使用多台机器的CPU、内存、存储,提供更好的性能。 数据库分库分表有 **两种实现方式:垂直拆分 和 水平拆分** 。 ### 2. 垂直拆分(Scale Up 纵向扩展) 垂直拆分分为 **垂直分库和垂直分表** ,主要 **按功能模块拆分,以解决** 各个库或者各个表之间的 **资源竞争** 。 比如分为订单库、商品库、用户库...这种方式,多个数据库之间的表结构是不同的。 #### 2.1 垂直分库 垂直分库其实 **是一种简单的逻辑分割** 。比如我们的 **数据库中有商品表Products、还有对订单表Orders,还有积分表Scores。** 接下来我们就可以创建三个数据库,一个数据库存放商品,一个数据库存放订单,一个数据库存放积分。 **垂直分库有一个优点,就是能够根据业务场景进行孵化** ,比如某一单一场景只用到某2-3张表,基本上应用和数据库可以拆分出来做成相应的服务。 拆分方式如下图所示: ![垂直分库](http://flt-pan.58heshihu.com/blog/typecho/lkjkmvcz.png) #### 2.2 垂直分表 垂直分表,比较 **适用于那种字段比较多的表** ,假设我们一张表有100个字段,我们分析了一下当前业务执行的SQL语句,有20个字段是经常使用的,而另外80个字段使用比较少。 这样我们就可以把20个字段放在主表里面,我们再创建一个辅助表,存放另外80个字段。 当然主表和辅助表都是有主键的,他们通过主键进行关联合并,就可以组合成100个字段的表。 拆分方式如下图所示: ![垂直分表](http://flt-pan.58heshihu.com/blog/typecho/lkjkoi6e.png) 除了这种访问频率的冷热拆分之外,还可以按照字段类型结构来拆分,比如大文本字段单独放在一个表中,与基础字段隔离,提高基础字段的访问效率。 也可以将字段按照功能用途来拆分,比如采购的物料表可以按照基本属性、销售属性、采购属性、生产制造属性、财务会计属性等用途垂直拆分。 **垂直拆分的优点** : > 跟随业务进行分割,类似微服务的分治理念,方便解耦之后的管理及扩展。 > 高并发的场景下,垂直拆分使用多台服务器的 CPU、I/O、内存能提升性能,同时对单机数据库连接数、一些资源限制也得到了提升,能实现冷热数据的分离。 **垂直拆分的缺点** : > 部分业务表无法 join,应用层需要很大的改造,只能通过聚合的方式来实现,增加了开发的难度。 > 单表数据量膨胀的问题依然没有得到有效的解决,分布式事务也是一个难题。 ### 3. 水平拆分(Scale Out 横向扩展) 水平拆分又 **分为库内分表和分库分表,来解决单表中数据量增长出现的压力,这些数据库中的表结构完全相同。** #### 3.1 库内分表 先说说库内分表。假设 **当我们的 Orders 表达到了 5000万 行记录的时候,非常影响数据库的读写效率** ,怎么办呢? 我们 **可以考虑按照订单编号的 order_id 进行 rang分区** ,就是把订单编号在 1-1000万 的放在order1表中,将编号在 1000万-2000万 的放在 order2 中,以此类推,每个表中存放 1000万 行数据。 ![库内分表](http://flt-pan.58heshihu.com/blog/typecho/lkjkz9e3.png) **关于水平分表的时机** ,业内的标准不是很统一, **阿里的Java 开发手册的标准是当单表行数超过 500万行或者单表容量超过 2 GB时,** 才推荐进行分库分表。 **百度的则是1000 W行的进行分表** ,这个是百度的DBA经过测试推算出的结果。 但是这边忽略了单表的字段数和字段类型,如果字段数很多,超过50列,对性能影响也是不小的,我们曾经有个业务,表字段是随着业务的增长而自动扩增的,到了后期,字段越来越多,查询性能也越来越慢。 所以个人觉得不必拘泥于500W 还是1000W,开发人员在使用过程中, **如果压测发现因为数据基数变大而导致执行效率慢下来,就可以开始考虑分表了。** #### 3.2 库内分表的实现策略 目前在 **MySql 中支持四种表分区的方式** ,分别为 **HASH、RANGE、LIST及KEY** ,当然在其它的类型数据库中,分区的实现方式略有不同,但是分区的思想原理是相同,具体如下: ##### 3.2.1 HASH(哈希) HASH 分区 **主要用来确保数据在预先确定数目的分区中平均分布** ,而在 RANGE 和 LIST 分区中,必须明确指定一个给定的列值或列值集合应该保存在哪个分区中,而在HASH 分区中,MySQL 自动完成这些工作, 你所要做的只是基于将要被哈希的列值指定一个列值或表达式,以及指定被分区的表将要被分割成的分区数量。 示例如下: ```mysql drop table if EXISTS `t_userinfo`; CREATE TABLE `t_userinfo` ( `id` int(10) unsigned NOT NULL, `personcode` varchar(20) DEFAULT NULL, `personname` varchar(100) DEFAULT NULL, `depcode` varchar(100) DEFAULT NULL, `depname` varchar(500) DEFAULT NULL, `gwcode` int(11) DEFAULT NULL, `gwname` varchar(200) DEFAULT NULL, `gravalue` varchar(20) DEFAULT NULL, `createtime` DateTime NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 PARTITION BY HASH(YEAR(createtime)) PARTITIONS 10; ``` 上面的例子,使用HASH函数对createtime日期进行HASH运算,并根据这个日期来分区数据,这里共分为 10 个分区。 建表语句上添加一个“PARTITION BY HASH (expr)”子句,其中“expr”是一个返回整数的表达式,它可以是字段类型为 MySQL 整型的一列的名字,也可以是返回非负数的表达式。 > 另外,可能需要在后面再添加一个“PARTITIONS num”子句,其中 num 是一个非负的整数,它表示表将要被分割成分区的数量。 ##### 3.2.2 RANGE(范围) **基于属于一个给定连续区间的列值,把多行分配给同一个分区,这些区间要连续且不能相互重叠** ,使用 **VALUES LESS THAN** 操作符来进行定义。示例如下: ```mysql drop table if EXISTS `t_userinfo`; CREATE TABLE `t_userinfo` ( `id` int(10) unsigned NOT NULL, `personcode` varchar(20) DEFAULT NULL, `personname` varchar(100) DEFAULT NULL, `depcode` varchar(100) DEFAULT NULL, `depname` varchar(500) DEFAULT NULL, `gwcode` int(11) DEFAULT NULL, `gwname` varchar(200) DEFAULT NULL, `gravalue` varchar(20) DEFAULT NULL, `createtime` DateTime NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 PARTITION BY RANGE(gwcode) ( PARTITION P0 VALUES LESS THAN(101) , PARTITION P1 VALUES LESS THAN(201) , PARTITION P2 VALUES LESS THAN(301) , PARTITION P3 VALUES LESS THAN MAXVALUE ); ``` 上面的示例,使用了范围RANGE函数对岗位编号进行分区,共分为4个分区, 岗位编号为 1~100 的对应在分区P0中,101~200 的编号在分区P1中,依次类推即可。那么类别编号大于 300,可以使用MAXVALUE来将大于 300 的数据统一存放在分区 P3中即可。 ##### 3.2.3 LIST(预定义列表) 类似于按 RANGE 分区,区别在于 LIST 分区 **是基于列值匹配一个离散值集合中的某个值来进行选择分区的。** LIST 分区 **通过使用“PARTITION BY LIST(expr)”来实现** ,其中 **“expr” 是某列值或一个基于某个列值、并返回一个整数值的表达式** , **然后通过“VALUES IN (value_list)”的方式来定义每个分区,其中“value_list”是一个通过逗号分隔的整数列表** 。 示例如下: ```,ysql drop table if EXISTS `t_userinfo`; CREATE TABLE `t_userinfo` ( `id` int(10) unsigned NOT NULL, `personcode` varchar(20) DEFAULT NULL, `personname` varchar(100) DEFAULT NULL, `depcode` varchar(100) DEFAULT NULL, `depname` varchar(500) DEFAULT NULL, `gwcode` int(11) DEFAULT NULL, `gwname` varchar(200) DEFAULT NULL, `gravalue` varchar(20) DEFAULT NULL, `createtime` DateTime NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 PARTITION BY LIST(`gwcode`) ( PARTITION P0 VALUES IN (46,77,89) , PARTITION P1 VALUES IN (106,125,177) , PARTITION P2 VALUES IN (205,219,289) , PARTITION P3 VALUES IN (302,317,458,509,610) ); ``` 上面的例子,使用了列表匹配LIST函数对员工岗位编号进行分区,共分为4个分区,编号为46,77,89的对应在分区P0中,106,125,177类别在分区P1中,依次类推即可。 不同于RANGE的是,LIST分区的数据必须匹配列表中的岗位编号才能进行分区,所以这种方式只是适合比较区间值确定并少量的情况。 ##### 3.2.4 KEY(键值) 类似于按HASH分区,区别在于 **KEY分区只支持计算一列或多列,且MySQL 服务器提供其自身的哈希函数。必须有一列或多列包含整数值。** 示例如下: ```mysql drop table if EXISTS `t_userinfo`; CREATE TABLE `t_userinfo` ( `id` int(10) unsigned NOT NULL, `personcode` varchar(20) DEFAULT NULL, `personname` varchar(100) DEFAULT NULL, `depcode` varchar(100) DEFAULT NULL, `depname` varchar(500) DEFAULT NULL, `gwcode` int(11) DEFAULT NULL, `gwname` varchar(200) DEFAULT NULL, `gravalue` varchar(20) DEFAULT NULL, `createtime` DateTime NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 PARTITION BY KEY(gwcode) PARTITIONS 10; ``` 注意: **此种分区算法目前使用的比较少** ,使用服务器提供的哈希函数有不确定性,对于后期数据统计、整理存在会更复杂,所以我们 **更倾向于使用由我们定义表达式的 Has** h,大家知道其存在和怎么使用即可。 ##### 3.2.5 Composite(复合模式) Composite 是上面几种模式的组合使用,比如你在 Range的基础上,再进行 Hash 哈希分区。 #### 3.3 分库分表 **库内分表解决了单表数据量过大的瓶颈问题,但使用还是同一主机的CPU、IO、内存,另外单库的连接数也有限制,并不能完全的降低系统的压力** 。 此时,我们就要考虑另外一种技术叫 **分库分表** 。分库分表 **在库内分表的基础上,将分的表挪动到不同的主机和数据库上。可以充分的使用其他主机的CPU、内存和IO资源** 。 拆分方式进一步演进到下面: ![分库分表](http://flt-pan.58heshihu.com/blog/typecho/lkjnxof0.png) ### 4. 分库分表存在的问题 #### 4.1 事务问题 在执行分库分表之后, **由于数据存储到了不同的库上,数据库事务管理出现了困难** 。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价;如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。 #### 4.2 跨库跨表的join问题 在执行了分库分表之后, **难以避免会将原本逻辑关联性很强的数据划分到不同的表、不同的库上** ,这时, **表的关联操作将受到限制** ,我们无法 join 位于不同分库的表,也无法 join 分表粒度不同的表,结果 **原本一次查询能够完成的业务,可能需要多次查询才能完成。** #### 4.3 额外的数据管理负担和数据运算压力 **额外的数据管理负担** ,最显而易见的就是 **数据的定位问题和数据的增删改查的重复执行问题** ,这些都可以通过应用程序解决,但必然 **引起额外的逻辑运算** ,例如,对于一个记录用户成绩的用户数据表 userTable,业务要求查出成绩最好的 100 位,在进行分表之前, **只需一个 order by 语句就可以搞定,但是在进行分表之后,将需要 n 个 order by 语句,分别查出每一个分表的前 100 名用户数据,然后再对这些数据进行合并计算,才能得出结果。** 最后修改:2023 年 07 月 28 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏