Featured image of post MySQL 分库分表

MySQL 分库分表

原因

随着互联网的发展和用户规模的迅速扩大,对系统的要求也越来越高。传统的 MySQL 单库单表架构的性能问题就暴露出来了。下面几个因素会影响数据库性能:

  • 数据量

    MySQL 单库数据量在 5000w 以内性能比较好,超过阈值后性能会随着数据量的增大而变弱。

    MySQL 单表数据量是 500w-1000w 之间性能比较好,超过 1000w 性能也会下降。

  • 磁盘

    单个服务的磁盘空间是有限制的,如果并发压力下,所有的请求都访问同一个节点,肯定会对磁盘 IO 造成非常大的影响。

  • 数据库连接

    数据库连接是非常稀少的资源,如果一个库里既有用户、商品、订单相关的数据,当海量用户同时操作时,数据库连接就很可能成为瓶颈。

🐟 垂直拆分

当我们单个库太大时,如果表的数量太多,则应该将部分表进行迁移(如按业务区分),这就是所谓的垂直切分。

🐠 垂直分库

针对一个系统中的不同业务进行拆分,比如用户相关的一个库,商品相关的一个库,订单相关的一个库。

垂直分库后将用户、商品、订单放到多个不同的服务器上,不会面临单机资源问题。这种做法与“微服务治理”的做法相似,每个微服务使用单独的一个数据库。

🐡 垂直分表

基于列字段进行的,“大表拆小表”。

一般是表中的字段较多,将不常用的,数据较大,长度较长(比如text类型字段)的字段数据拆分到“扩展表“。

针对那种几百列的大表,也避免查询时,数据量太大造成的“跨页”问题。

MySQL 底层是通过数据页存储的,一条记录占用空间过大会导致跨页,造成额外的性能开销(IO操作变多)。

数据也是以页为单位加载到内存中,页中存储的是行数据,大小固定,一行数据占用空间越小,页中存储的行数据也就越多。这样表中字段长度较短且访问频率较高,内存中能加载更多的数据,命中率更高,减少了磁盘IO,从而提升了数据库性能。

优缺点

  • 优点:

    • 解决业务系统层面的耦合,业务清晰
    • 能对不同业务的数据进行分级管理、维护、监控、扩展等
    • 高并发场景下,能提升一定的磁盘 IO,数据库连接数,单机硬件资源的瓶颈
  • 缺点:

    • 部分表无法 join
    • 分布式事务处理复杂
    • 还是存在单表数据量过大的问题

🐥 水平拆分

如果数据量行数巨大,存在单库读写、存储性能瓶颈,这时候就需要进行水平切分了。

🐤 水平库内分表

水平分表是基于全表的,将一个表中的数据拆分到多个表中,可以大大减少单表数据量,提升查询效率。

库内分表只是解决了单一表数据量过大的问题,没有将表分布到不同机器的库上,对于减轻 MySQL 数据库的压力来说,帮助不是很大,还是竞争同一个物理机的资源。

🐣 水平分库分表

将单张表的数据切分到多个服务器上去,每个服务器具有相同的库与表,但是表中的数据不同。

水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈。

优缺点

  • 优点

    • 不存在单库数据量过大,高并发的性能瓶颈,提升系统的稳定性和负载能力
    • 应用端改造较小,不需要拆分业务模块
  • 缺点

    • 跨分片的事务一致性难以保证
    • 跨库的 join 关联查询性能较差
    • 数据多次扩展难度和维护量极大

常见分库分表的策略

🔢 根据数值范围划分

按照时间区间或 ID 区间进行区分。如:

  • 🧩 按日期将不同月或不同日的分散到不同库中
  • 🧩 根据 id 分,将 [0, 1000000] 分到第一个库,[1000001, 2000000] 分到第二个库
  • 🧩 “冷热数据分离”,一些使用较少的历史数据迁移到其他库中,业务功能上只提供热点数据的查询

优点:

  • 单表大小可控
  • 天然便于水平扩展,扩容只需要添加节点,无需对其他分片数据进行迁移
  • 在范围查找时,连续分片可以快速进行定位查询

缺点:

  • 热点数据成为瓶颈。连续分片可能存在热点数据,如按时间分片时,有些最近时间的分片会被频繁读取,有些历史分片会很少查询

🧮 根据数值取模

一般采用 hash 取模的分片方式。

如根据用户 user_no 字段取余(根据数据库的数量取余),余数为 0 放到第一个库,余数为 1 放到第二个库,以此类推。(同一个用户会定位到相同的数据库)

优点:

  • 数据分片相对来说比较均匀,不容易出现热点和并发访问的瓶颈。

缺点:

  • 后期分片扩容时,需要迁移旧数据(使用一致性 hash 算法能较好避免)
  • 容易遇到分片查询复杂问题,如通过 user_no 进行的分库分表,如果有多数查询条件中不包含 user_no 字段,就无法定位数据库,需要在所有的数据库发起查询

🌐 根据地理位置

根据地理位置,将相同地区的放到一张表中。如:华南区一个表,华北区一个表。

常见分库分表的问题

事务一致性问题

  • 分布式事务

    分布式事务能最大限度保证了数据库操作的原子性。

    • 使用分布式事务中间件
    • 使用 MySQL 自带的针对跨库的事务一致性方案(XA),性能要比单库的慢 10 倍左右。
    • 在业务上,避免跨库操作(如将用户和商品放到同一个库中)
  • 最终一致性

不苛求系统的实时一致性,只要在允许的时间段内达到最终一致性即可,可采用事务补偿的方式。

事务补偿是一种事后检查补救的措施,一些常见的实现方法有:对数据进行对账检查,基于日志进行对比,定期同标准数据来源进行同步等等。

跨节点关联查询 join 问题

  • 多次查询进行数据组装

    在系统层面,分两次查询,最后将获得到的数据进行字段拼装。

  • 添加冗余字段

    一种典型的反范式设计,利用空间换时间,为了性能而避免 join 查询。

    但这种方法适用场景有限,适用于依赖字段比较少的情况。而冗余字段的数据一致性也较难保证,要结合实际业务场景进行考虑。

跨节点分页、排序、函数问题

先在不同的分片节点中将数据进行操作,在服务层进行汇总操作,最终返回给用户。

主键问题

  • UUID(不推荐)

  • 使用 Redis 生成主键(incr 时自增的原子命令)

  • 使用 Zookeeper 生成唯一 ID

  • 雪花算法生成

  • 百度 uidgenerator

  • 美团 Leaf

  • 滴滴 TinyID

数据迁移,扩容问题

  • 如果采用数值范围分片,只需要添加节点就可以进行扩容了,不需要对分片数据迁移。

  • 如果采用的是数值取模分片,针对数据量的递增,可能需要动态的增加表,因为reHash有可能导致数据迁移问题,则考虑后期的扩容问题就相对比较麻烦。

考虑切分的情况

  • 能不切分就尽量不要切分

    不到万不得已不用轻易使用分库分表这个大招,避免"过度设计"和"过早优化"。

  • 数据量过大,正常运维影响业务访问

    • 对数据库备份,如果单表太大,备份时需要大量的磁盘 IO 和网络 IO
    • 对一个很大的表进行 DDL 修改时,MySQL会锁住全表,这个时间会很长,这段时间业务不能访问此表,影响很大
    • 大表会经常访问与更新,就更有可能出现锁等待
  • 随着业务发展,数据量快速增长

  • 安全性和可用性

    • 利用垂直切分,一个数据库出现问题,只会影响到部分业务,不会使所有的业务都瘫痪。
    • 利用水平切分,当一个数据库出现问题时,不会影响到100%的用户,每个库只承担业务的一部分数据,这样整体的可用性就能提高。

常见分库分表中间件

  • sharding-jdbc

  • MyCat

参考

Licensed under CC BY-NC-SA 4.0