MySQL 分库分表第一节

一、什么时候需要分库分表?

我们常常听或做分库分表,但具体什么时候去做分库分表呢?其实它没有一个严格答案,如果非要说出一个答案,就是当单库单表下MySQL读写的速度无法忍受了。
当然也有一些业内最佳实践:单库数据量超5000W行或大于100G,单表数据量超500W行或大于2G下MySQL运行性能下降较快,这个是根据阿里巴巴《Java 开发手册》提出单表行数超过 500 万行或者单表容量超过 2GB,推荐进行分库分表来的。
不过事无绝对,据D.V.B 团队以及Cmshelp 团队做CMS 系统评测时的结果来看,MySQL单表大约在2千万条记录(4G)下能够良好运行,经过优化后5千万条记录(10G)下运行良好。毕竟影响MySQL性能的还有服务器(CPU、内存、I/O、硬盘),网络等硬件,如果硬件跟得上,SQL优化到位(表结构、字段、索引,基础sql语句、join、分页等合理),5000W难,但MySQL hold住1000W还是轻松的。
说了这么多,就是想说还是要结合实际情况(硬件等条件)来做,评估优化成本和实际收益是否合理。当收益不高的时候,就可以分库分表了,如果内心实在没有标准去权衡或难以评估,500W(2G)这个业内最佳实践可以直接拿来用的,相对大多服务器配置来说算是一个比较中肯的数值。
总之我的观点就是:

一开始不采用分库与分表设计,而是随着业务的增长,在无法继续优化的情况下,再考虑分库与分表提高系统的性能,另外并不是所有表都需要进行切分,主要还是看数据的增长速度。切分后会在某种程度上提升业务的复杂度,数据库除了承载数据的存储和查询外,协助业务更好的实现需求也是其重要工作之一。
不到万不得已不用轻易使用分库分表这个大招,避免"过度设计"和"过早优化"。分库分表之前,不要为分而分,先尽力去做力所能及的事情,例如:慢查询优化、索引优化、升级硬件、升级网络、读写分离(主从架构)、增加缓存层(Redis等)等等。当数据量达到单表的瓶颈时候,再考虑分库分表。

MySQL单表数据量超过2000万性能急剧下降的说法靠谱?
单机数据库优化的一些实践(mysql)

二、为什么要分库分表?

其实,前面概括性的说过,单库单表下MySQL读写的速度无法忍受了。索引什么的都加了,一个普通的DQL竟然还要我几秒,士可忍孰不可忍啊。

待后续。。。。。。

三、有哪些分库分表方案?

数据库分布式架构核心内容无非就是数据分片(Sharding),以及分片后对数据的定位、整合。数据分片就是将数据分散存储到多个数据库中,使得单一数据库中的数据量变小,通过扩充主机的数量缓解单一数据库的性能问题,从而达到提升数据库操作性能的目的。
目前主流有两种:垂直(纵向)切分,水平(横向)切分。

3.1、 垂直(纵向)切分

垂直切分常见有垂直分库和垂直分表两种。

3.1.1垂直分库

垂直分库,其切分标准一般是按业来分,就是根据业务耦合性,将关联度低的不同表存储在不同的数据库。做法与大系统拆分为多个小系统类似,按业务分类进行独立划分。与"微服务治理"的做法相似,每个微服务使用单独的一个数据库。如图:
MySQL 分库分表第一节_第1张图片通过这种分布式架构将耦合性强的业务所需的所有表一起放在独立的库中,不同业务(库)之间不需要关联查询即可完成需求,如果需要,就证明划分失败,可通过冗余字段或表来解决。

  • 垂直切分的优点:
    1:解决业务系统层面的耦合,业务清晰;
    2:与微服务的治理类似,也能对不同业务的数据进行分级管理、维护、监控、扩展等;
    3:高并发场景下,垂直切分一定程度的提升IO、数据库连接数、单机硬件资源的瓶颈。

  • 缺点
    1:部分表无法join,只能通过接口聚合方式解决,提升了开发的复杂度;
    2:分布式事务处理复杂;
    3:依然存在单表数据量过大的问题(需要水平切分)。

3.1.2垂直分表

垂直分表简单的说就是将字段过多的表分成两个以上的表,按字段大小,查询使用频率来拆,一般字段小(如int),查询使用频率高的放在一起,字段大(如text)或使用频率低的放在一起。
例如一个表有100多个字段(腾讯面试官说它们有的表这么多字段,不知真假哈哈),这样将大表分小表,便于开发维护。另外MySQL底层存储数据是Tablespace、Segment、Extent、Page、 Row这样结构来存储数据,一个Page(默认16kb),如果Row过大就会出现行溢出,增加额外开销,分成小表避免了这个问题。另外,数据库以行为单位将数据加载到内存中,这样表中字段长度越短且访问频次较高,内存能加载更多的数据,命中率更高,减少磁盘IO,从而提升数据库的性能。

3.2、水平(横向)切分

当一个应用难以再细粒度的垂直切分,或切分后数据量行数巨大,存在单库读写、存储性能瓶颈,这时候就需要进行水平切分了。
水平切分分为库内水平分表和水平分库分表。

3.2.1 库内水平分表

方案如下:
MySQL 分库分表第一节_第2张图片
但库内分表只解决了单一表数据量过大的问题,但没有将表分布到不同机器的库上,因此对于减轻MySQL数据库的压力来说,帮助不是很大,大家还是竞争同一个物理机的CPU、内存、网络IO,最好通过分库分表来解决。

3.2.2 水平分库分表

方案如下:
MySQL 分库分表第一节_第3张图片

  • 水平分库分表的优点:
    1: 不存在单库数据量过大、高并发的性能瓶颈,提升系统稳定性和负载能力;
    2:应用端改造较小,不需要拆分业务模块。
  • 缺点:
    1: 跨分片的事务一致性难以保证;
    2:跨库的join关联查询性能较差;
    3: 数据多次扩展难度和维护量极大。
3.3、水平(横向)切分的策略

水平切分策略,个人感觉来源于MySQL自带的分区策略。主要有RANGE Partitioning和HASH Partitioning。库内水平分表与水平分库分表的区别是前者映射到表,后者映射到库。

3.3.1 范围切分

按照时间区间或ID区间来切分。以用户业务举例:按日期将不同月甚至是日的数据分散到不同的库/表中;按照userId为1-9999的记录分到第一个库/表,10000-20000的分到第二个库/表,以此类推。某种意义上,某些系统中使用的"冷热数据分离",将一些使用较少的历史数据迁移到其他库中,业务功能上只提供热点数据的查询,也是类似的实践。

  • 优点:
    1:单表大小可控;
    2:天然便于水平扩展,后期如果想对整个分片集群扩容时,只需要添加节点即可,无需对其他分片的数据进行迁移;
    3:使用分片字段进行范围查找时,连续分片可快速定位分片进行快速查询,有效避免跨分片查询的问题。

  • 缺点:
    数据分布不均匀, 热点数据成为性能瓶颈。连续分片可能存在数据热点,例如按时间字段分片,有些分片存储最近时间段内的数据,可能会被频繁的读写,而有些分片存储的历史数据,则很少被查询,如下图所示:
    MySQL 分库分表第一节_第4张图片

3.3.1 HASH切分

一般采用hash取模的切分方式,仍以用户业务举例:首先对用户ID进行取模操作,根据 user_id%100 获取对应的表进行存储查询操作,余数为0的放到第一个库/表,余数为1的放到第二个库/表,以此类推。示意图如下:

图3.3.1-1 库内水平分表示意图

MySQL 分库分表第一节_第5张图片 图3.3.1-2 水平分库分表示意图
MySQL 分库分表第一节_第6张图片

  • 优点:
    数据分片相对比较均匀,不容易出现热点和并发访问的瓶颈
  • 缺点:
    1:后期分片集群扩容时,需要迁移旧的数据(使用一致性hash算法能较好的避免这个问题)
    2: 容易面临跨分片查询的复杂问题。比如上例中,如果频繁用到的查询条件中不带cusno时,将会导致无法定位数据库,从而需要同时向4个库发起查询,再在内存中合并数据,取最小集返回给应用,分库反而成为拖累。
3.4 分库分表带来的问题

分库分表能有效的环节单机和单库带来的性能瓶颈和压力,突破网络IO、硬件资源、连接数的瓶颈,同时也带来了一些问题。下面将描述这些技术挑战以及对应的解决思路。
1、事务一致性问题
分布式事务

当更新内容同时分布在不同库中,不可避免会带来跨库事务问题。跨分片事务也是分布式事务,没有简单的方案,一般可使用"XA协议"和"两阶段提交"处理。

分布式事务能最大限度保证了数据库操作的原子性。但在提交事务时需要协调多个节点,推后了提交事务的时间点,延长了事务的执行时间。导致事务在访问共享资源时发生冲突或死锁的概率增高。随着数据库节点的增多,这种趋势会越来越严重,从而成为系统在数据库层面上水平扩展的枷锁。
最终一致性

对于那些性能要求很高,但对一致性要求不高的系统,往往不苛求系统的实时一致性,只要在允许的时间段内达到最终一致性即可,可采用事务补偿的方式。与事务在执行中发生错误后立即回滚的方式不同,事务补偿是一种事后检查补救的措施,一些常见的实现方法有:对数据进行对账检查,基于日志进行对比,定期同标准数据来源进行同步等等。事务补偿还要结合业务系统来考虑。
分布式事务常用的解决方案

2、跨节点关联查询 join 问题

切分之前,系统中很多列表和详情页所需的数据可以通过sql join来完成。而切分之后,数据可能分布在不同的节点上,此时join带来的问题就比较麻烦了,考虑到性能,尽量避免使用join查询,可以说如果使用分库分表就基本与关联查询告别了。
目前粗略的解决方法:
1:全局表:基础数据,所有库都拷贝一份。
2:字段冗余:这样有些字段就不用join去查询了,如在订单表中存入用户名称等冗余字段。
3:业务层组装:分别查询出不同节点数据,然后组装起来,较复杂。
4:ER分片:在关系型数据库中,按ER图中表之间的关联紧密程度,将关系足够紧密的表放在一个分片(节点)中,如订单表与订单详情表,可以做到片内联表查询,这样做就类似于垂直切分是根据业务分。

3、跨节点分页、排序、(聚合)函数问题
4、全局主键避重问题

  1. UUID
    UUID标准形式包含32个16进制数字,分为5段,形式为8-4-4-4-12的36个字符,例如:550e8400-e29b-41d4-a716-446655440000
    UUID是主键是最简单的方案,本地生成,性能高,没有网络耗时。但缺点也很明显,由于UUID非常长,会占用大量的存储空间;另外,作为主键建立索引和基于索引进行查询时都会存在性能问题,在InnoDB下,UUID的无序性会引起数据位置频繁变动,导致分页。

  2. Snowflake分布式自增ID算法
    Twitter的snowflake算法解决了分布式系统生成全局ID的需求,生成64位的Long型数字,组成部分:

    第一位未使用
    接下来41位是毫秒级时间,41位的长度可以表示69年的时间
    5位datacenterId,5位workerId。10位的长度最多支持部署1024个节点
    最后12位是毫秒内的计数,12位的计数顺序号支持每个节点每毫秒产生4096个ID序列

5、数据迁移、扩容问题
业务高速发展,面临性能和存储的瓶颈时,才会考虑分片设计,此时就不可避免的需要考虑历史数据迁移的问题。一般做法是先读出历史数据,然后按指定的分片规则再将数据写入到各个分片节点中。此外还需要根据当前的数据量和QPS,以及业务发展的速度,进行容量规划,推算出大概需要多少分片(一般建议单个分片上的单表数据量不超过1000W)

如果采用数值范围分片,只需要添加节点就可以进行扩容了,不需要对分片数据迁移。如果采用的是数值取模分片,则考虑后期的扩容问题就相对比较麻烦。

3.4 分库分表中间件

目前有很多开源的支持分库分表中间件,本人用过基于Cobar的MyCat,mycat文档。
MySQL 分库分表第一节_第7张图片

3.5 参考

1】数据库分区、分表、分库、分片;
2】数据库Sharding的基本思想和切分策略;
3】数据库分库分表策略的具体实现方案;
4】MySQL 分库分表方案
5】mysql分表的3种方法

你可能感兴趣的:(mysql)