我们为什么需要分库分表
在讨论分库分表之前,我们需要明确为什么要进行这样的操作。我们从两个维度来分析:为什么要分库,为什么要分表。
1. 为什么要分库
- 磁盘存储:当业务量剧增,单机MySQL的磁盘容量可能会达到极限。通过将数据拆分到多个数据库中,可以大幅降低单库的磁盘使用率。
- 并发连接支撑:数据库连接数是有限的。在高并发场景下,单机MySQL无法承受大量请求,可能会出现“too many connections”报错。为了应对高并发,可以采用微服务架构,将不同模块拆分为多个应用,并将单一数据库拆分为多个功能模块的数据库(如订单库、用户库、商品库),以分散读写压力。
2. 为什么要分表
当单表数据量非常大时,存储和查询性能会遇到瓶颈。即使做了很多优化,仍然可能无法提升效率。这时需要考虑分表。一般来说,当单表数据量达到千万级别时,就需要分表。
这是因为,即使SQL命中了索引,如果表的数据量超过千万,查询速度也会明显变慢。B+树索引结构的高度会随着数据量的增加而增加,从而导致查询速度变慢。假设一棵高度为3的B+树,可以存放约两千万条记录。如果B+树高度达到4层,查询时需要多次查找磁盘,SQL执行速度会变慢。
什么时候考虑分库分表?
对于MySQL的InnoDB存储引擎,单表最多可以存储10亿级数据,但性能会很差。阿里巴巴《Java开发手册》建议:
单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表。
但是我们不应等到数据量达到500万才开始分库分表,而是应该提前规划。如果估算3年后,表数据量不会达到500万,则不需要分库分表。如果持续发展,仍需考虑分库分表。一般流水表、用户表等类型的业务表需要考虑分库分表,而配置类的表则不需要。
如何选择分表键
分表键即用来分库/分表的字段,如用户ID、时间、地区等。数据库表拆分时,需先找到业务的主题。例如企业客户信息表可以使用客户号作为分表键,以将同一客户的信息存储在一个表中,避免全表路由。
非分表键如何查询
分库分表后,有时需要通过非分表键来查询。例如按userId分表,但用户登录时需要根据手机号查询用户信息。非分表键查询方案包括:
- 遍历所有表(不推荐)
- 将用户信息冗余同步到ES,通过ES查询(推荐)
- 基因法:如订单号包含客户号,通过订单号解析客户号(不适用于手机号)
分库后,事务问题如何解决
分库分表后,本地事务无效,需要使用分布式事务。常用分布式事务解决方案包括:
- 两阶段提交
- 三阶段提交
- TCC
- 本地消息表
- 最大努力通知
- saga
跨节点Join关联问题
分库分表后,跨库join操作可以通过以下方式解决:
- 字段冗余:将关联字段放入主表,避免关联操作。
- 全局表:基础表在每个数据库中均保存一份。
- 数据抽象同步:定时将关联表数据同步,生成新表(借助ETL工具)。
- 应用层代码组装:分开多次查询,代码层进行字段拼装。
order by、group by等聚合函数问题
跨节点的count
、order by
、group by
等聚合函数问题,可以分别在各个节点上得到结果后,再在应用程序端合并。
分库分表后的分页问题
方案1(全局视野法)
在各个数据库节点查询结果后,在代码端汇聚再分页。优点是精准返回所需数据,缺点是返回数据多,增大网络传输,造成空查。
方案2(业务折衷法-禁止跳页查询)
业务妥协,只允许上一页和下一页,不允许跳页查询。查询第一页时,与全局视野法相同。下一页时,将当前最大创建时间传递,每个节点查询大于该时间的一页数据,汇总后返回。
分库分表选择哪种中间件
常见分库分表中间件包括:
-
Sharding-JDBC:当当开源,建议使用
-
cobar:阿里巴巴产品,不支持读写分离
-
Mycat:建议使用,功能强大
-
Atlas:360开源产品,不支持分布式分表
-
TDDL(淘宝):阿里巴巴产品,不支持读写分离
-
vitess:谷歌产品,支持高并发,使用较少
如何评估分库数量
单库处理记录能力决定分库数量。一般单库超过5000万记录时,DB压力大。分库数量应在4~10个之间,一般建议10个库以下,便于管理。
垂直分库、水平分库、垂直分表、水平分表的区别
- 水平分库:以字段为依据,将数据拆分到多个库中。
- 水平分表:以字段为依据,将数据拆分到多个表中。
- 垂直分库:以表为依据,将不同表拆分到不同库中。
- 垂直分表:以字段为依据,将表中字段拆到不同表(主表和扩展表)中。
分表要停服吗?不停服怎么做?
分表不需要停服。具体步骤如下:
- 编写代理层,加开关控制访问新旧DAO,灰度发布期间访问旧DAO。
- 发版全量后,开启双写,同时写入新旧表,记录新表ID起始值,旧表中小于该值的数据为存量数据。
- 通过脚本将旧表存量数据写入新表。
- 停读旧表改读新表,保持双写一段时间。
- 一段时间后,无业务问题,停写旧表。