NOTE
数据库分库分表
1. 为什么需要分库分表 分布式系统分区.md 2. 分库分表是什么 分库是把原来的一个库分成多个库,分表是把原来的一张表分成多张表 分库和分表都有水平拆分和垂直拆分的方式 2.1. 垂直拆分 2.1.1. 垂直分表 每个表的结构都不一样,每个表的数据也不一样 把一张表的字段按照使用频率、是否大字段分成多张表(一对一关系)。比如把商品信息表分成商品基本信息表和商品描述表 这个操作一般在设计之初完成 2.1.2. 垂直分库 每个库的结构都不一样,每个库的数据也不一样 按照业务逻辑耦合程度把一个库分成多个库,部署在不同机器上。本质上和微服务按照业务拆分一个逻辑,每个服务对应一个数据库,比如商品信息
这是历史学习笔记,可能存在过时或不完整的理解。
1. 为什么需要分库分表
2. 分库分表是什么
分库是把原来的一个库分成多个库,分表是把原来的一张表分成多张表 分库和分表都有水平拆分和垂直拆分的方式
2.1. 垂直拆分
2.1.1. 垂直分表
每个表的结构都不一样,每个表的数据也不一样 把一张表的字段按照使用频率、是否大字段分成多张表(一对一关系)。比如把商品信息表分成商品基本信息表和商品描述表 这个操作一般在设计之初完成
2.1.2. 垂直分库
每个库的结构都不一样,每个库的数据也不一样 按照业务逻辑耦合程度把一个库分成多个库,部署在不同机器上。本质上和微服务按照业务拆分一个逻辑,每个服务对应一个数据库,比如商品信息库可以分为商品库和店铺库 这个操作一般在设计之初完成
2.2. 水平拆分
2.2.1. 水平分表
每个表的结构都一样,每个表的数据都不一样
单表的行数很大,需要数据行数分开到不同表中.比如商品表有50W数据,那么分成5份表。每份有10W数据 数据量中等规模时能小幅度提升性能,数据量一大还是得用水平分库
2.2.2. 水平分库
每个库的结构都一样,每个库的数据都不一样 单表的行数很大,需要按照数据行数分开到不同数据库中.比如商品库有500W数据,那么分成50份库。每份有10W数据 这个操作是在数据库索引优化、加缓存、读写分离之后,还有性能问题时才使用
3. 分库分表带来的问题
3.1. 主键ID唯一的问题
3.2. 事务一致性问题
3.3. 跨节点排序、聚合、分页问题
- 并发查询在代码层对结果封装。比如排序,可以发送请求给所有节点,取出前N条数据,聚合后排序取出前N条。参考Elasticsearch CRUD流程.md
3.4. 跨节点Join的问题
- 使用单表查询然后在代码层对结果封装
- 做宽表,重写轻读
- 使用搜索引擎
4. 分库分表中间件
4.1. 分类
- 主要分为client和proxy两类
- sharding-jdbc(client)
- mycat(proxy)
4.2. Sharding JDBC
4.2.1. 是什么
一个客户端的分库分表中间件,可以理解为增强版的JDBC驱动,核心功能有数据分片、读写分离。
4.2.2. 不支持项
子查询必须有sharding-column 不支持批量插入 聚合函数必须取别名 sql不能有括号,比如limit语句
5. 水平拆分方案
5.1. 拆分数据
5.1.1. 拆分Key的选择
主键ID或者业务ID或者时间,兼顾查询效率和拆分均匀
5.1.2. 拆分策略
5.1.2.1. 单独的映射表
- 单独的映射表
- 优点:ID和库的Mapping算法可以随意更改。
- 缺点:
- 映射库数据量大了也得分库分表
- 写入需要同时写业务库和映射库,属于分布式事务问题。可以通过定时任务对比保证最终一致性
5.1.2.2. 范围切分/顺序切分

- 优点:单表大小可控,天然水平扩展。
- 缺点:会有热点数据的问题。新增加的数据集中在一个节点,会有写入瓶颈
- 适合时间拆分用于冷热分离场景
5.1.2.3. Hash切分

- 优点:基本不会有热点问题
- 缺点:扩容时需要迁移数据
- 必须用一致性Hash,要么是跟Redis一样的hash slot,即提前规划好库的数量;要么是hash ring
5.2. 请求处理
5.2.1. 路由组件
要么是客户端,要么是代理
5.2.2. 一级索引
5.2.3. 二级索引
- 根据非sharding key查,那么需要发送请求给所有节点,最后聚合。但是效率低
5.2.3.1. 冗余两份数据
冗余两份数据,一份按ID1分库,另一份按ID2分库。写入的时候可以业务双写,也可以业务单写+bin log监听
5.2.3.2. 统一维度
比如ID1作为ID2的前几位,这样ID2就包含了ID1的信息。按ID1查询的时候没问题,按ID2查询的时候先取出ID1,再进行查询
5.3. 分区分配
分布式系统分区之分区分配.md 就是扩缩容时的数据迁移,需要手动处理
5.3.1. 停机
在凌晨没有用户的时候停机,然后把旧的单库的数据拉出来,重新刷到新的分库中,刷完之后修改代码连分库对外服务
5.3.2. 不停机双写
修改代码,写数据的时候同时写旧的单库和新的分库,对于旧库的数据,用脚本取出来重新刷到分库
6. 单表数据量多大得考虑分库分表
2KW,因为对应的树高为3,IO次数大概为3
MySQL InnoDB存储引擎的主键索引对应的B+树,假设page size为16KB,主键是bigint类型的,每行记录大小为1KB,那么三层可以存放多少条记录?
page size为16KB,key为8B,指针为6B,那么一个page可以存放16*1024/(8+6)=1170个key
page size为16KB,value为1KB,那么一个page可以存放16/1=16个value
假设树高为3,那么
第0层:root层。常驻内存
第一层:key层。可以存放1170个key,对应1170个分叉
第二层:key层。可以存放1170*1170个key,对应1368900个分叉
第三层:value层。可以存放1170*1170*16个value,对应21902400(大概2KW)行
。
前两个key层大概占用(1170+1368900)*16/1024/1024=21MB,这个层次内存可以缓存索引,再高一层那么(1170+1368900+1601613000)*16/1024/1024=24GB,内存缓存不下。最后的value层大概占用21902400/1024/1024=21GB磁盘空间
7. 参考
- 10亿级订单系统分库分表设计思路!
- 大众点评订单系统分库分表实践 - 美团技术团队
- docs/high-concurrency/database-shard-method.md · shishan100/Java-Interview-Advanced - Gitee.com
- 一种简单实用、支持动态扩缩容的分库分表方案(内部链接已脱敏)
- 面试官:MySQL innnodb存储引擎的主键索引对应的B+树,假设page size为64KB,主键是int类型的,每行记录大小为1KB,那么三层可以存放多少条记录?
- 面试官:MYSQL单表数据达2000万性能严重下降,为什么?我:额,不知道.. - 知乎
- MySQL单表数据不要超过500万行:是经验数值,还是黄金铁律? - SegmentFault 思否

讨论
使用 GitHub 账号参与讨论,评论会保存在 GitHub Issues 中。在 GitHub 查看