RSS
菜单
全部文章快讯开发科技深度热点

第 80 章 速查卡:分库分表与分布式

内容摘要

分库分表是大数据量场景的终极解决方案,面试常考。本篇整理分库分表的核心概念、分片策略、分布式 ID、分布式事务、跨分片查询等考点,用表格速查。看完这篇,分库分表面试题基本能答上来。

📖 本篇简介:分库分表是大数据量场景的终极解决方案,面试常考。本篇整理分库分表的核心概念、分片策略、分布式 ID、分布式事务、跨分片查询等考点,用表格速查。看完这篇,分库分表面试题基本能答上来。

目录

  • 速查:分库分表核心概念
  • 分片策略对比表
  • 分布式 ID 方案对比
  • 分布式事务方案对比
  • 跨分片查询方案
  • 分库分表中间件对比
  • 分库分表常见问题
  • 面试高频问答
  • 自测

速查:分库分表核心概念

分库分表分垂直拆分和水平拆分。垂直拆分按业务拆库,水平拆分按数据行拆表。

| 概念 | 说明 | 适用场景 |

|---|---|---|

| 垂直分库 | 按业务拆库,用户库、订单库、商品库 | 业务边界清晰 |

| 垂直分表 | 把大字段拆到扩展表 | 单表列太多 |

| 水平分库 | 一个库的数据拆到多个库 | 单库数据量大 |

| 水平分表 | 一个表的数据拆到多个表 | 单表数据量大 |

| 分片键 | 分库分表用的字段 | 高频查询字段 |

| 分片数 | 分多少个库表 | 提前规划好 |

| 路由 | 怎么定位数据在哪个分片 | 中间件做 |

| 主键 | 全局唯一 ID | 分布式 ID |

什么时候该分库分表:单表千万级、单库 QPS 到瓶颈、业务垂直拆分后某个模块还是大。

分库分表代价:跨库 join、分布式事务、运维复杂。能不分就不分。

垂直分库例子:用户库、订单库、商品库分开,各管各的。

水平分表例子:订单表按用户 ID 哈希拆成 order_0、order_1 ... order_15。

为什么要分片键:没有分片键,不知道数据在哪个分片,查询要扫所有分片。

分片键不能更新:分片键变了,数据要迁移到别的分片,代价大。

分库分表本质:把一个大库拆成多个小库,每个小库压力小了。

和读写分离的区别:读写分离是一主多从,读扩展;分库分表是把数据拆开,写扩展。

什么时候垂直分库:业务多了,按业务域拆开,各管各的库。

什么时候水平分表:单表数据太大,一个库扛不住。

分库分表的好处:每个分片数据小,查询快,写压力分散。

分库分表的坏处:跨库 join、分布式事务、运维复杂。

垂直分库例子:用户库、订单库、商品库。

水平分库例子:订单表按用户 ID 哈希拆到 db0、db1。

分库分表和读写分离能不能一起用:能,读写分离读扩展,分库分表写扩展。

什么时候用分库分表:单表千万级、单库 QPS 到瓶颈。

分片策略对比表

分片策略选不好,后面全是坑。常见的有这几种。

| 策略 | 原理 | 优点 | 缺点 | 适用 |

|---|---|---|---|---|

| 范围分片 | 按 ID 或时间范围 | 扩容简单 | 热点问题 | 日志、订单 |

| 哈希分片 | 按 ID 哈希取模 | 数据均匀 | 扩容麻烦 | 用户表 |

| 一致性哈希 | 哈希环 | 扩容迁移少 | 实现复杂 | 缓存 |

| 按时间分片 | 按月或按年分表 | 归档方便 | 跨年查询麻烦 | 日志、报表 |

分片键怎么选:选高频查询字段,尽量让查询都落在一个分片。

热点问题:范围分片容易热点,最新数据都打在最新分片。解决:哈希打散。

扩容问题:哈希取模扩容要迁移大部分数据。解决:一致性哈希。

按时间分片例子:日志表按月份分,log_202601、log_202602,老数据直接归档。

范围分片例子:用户 ID 1~100 万在一个分片,100~200 万在下一个。

哈希分片例子:用户 ID % 16,拆成 16 个分片。

按时间分片优缺点:归档方便,但跨年查询要扫很多表。

分片数怎么定:提前规划,一般 4、8、16 个,方便扩容。

分片键选用户 ID 还是订单 ID:看业务,按用户查多就选用户 ID。

热点问题怎么解决:哈希打散,不要按时间连续分。

预分片是什么:提前多分几个分片,后面扩容直接用预留的。

取模扩容为什么麻烦:原来 4 个分片,加到 8 个,每个分片都要迁一半数据。

一致性哈希怎么解决扩容:哈希环上只迁相邻节点的数据,迁移少。

虚拟节点是什么:一致性哈希加虚拟节点,让数据分布更均匀。

按时间分片例子:日志表按月分,log_202601、log_202602。

范围分片热点问题:最新数据都打在最新分片,压力大。

哈希分片优点:数据均匀,没有热点。

-- 哈希分片路由示例(本示例基于 MySQL 8.0)
-- 用户 ID % 16 决定落在哪个分片
SELECT * FROM order_${user_id % 16} WHERE user_id = 123456;

分布式 ID 方案对比

分库分表后,自增主键不能用了,要用分布式 ID。

| 方案 | 原理 | 优点 | 缺点 |

|---|---|---|---|

| UUID | 随机生成 | 简单 | 太长、无序 |

| 雪花算法 | 时间戳+机器 ID+序列号 | 有序、高性能 | 时钟回拨 |

| 号段模式 | 数据库取号段 | 简单 | 依赖数据库 |

| Redis INCR | Redis 自增 | 简单 | 依赖 Redis |

推荐:雪花算法,高性能、有序、不依赖外部组件。

雪花算法结构:1 位符号位 + 41 位时间戳 + 10 位机器 ID + 12 位序列号。

时钟回拨问题:机器时钟回拨会生成重复 ID。解决:回拨小就等,回拨大就报错。

号段模式原理:数据库存一个最大 ID,应用一次取一段(比如 1000 个),用完再取。

Redis INCR 原理:Redis 计数器自增,性能高,但要持久化防丢。

UUID 为什么不推荐做主键:太长、无序,InnoDB 聚簇索引插入性能差。

号段模式优缺点:简单,数据库压力小,但号段用完要等下次取。

雪花算法性能:单机每秒几百万 ID,完全够用。

雪花算法什么时候用:分布式系统主键不重复的场景。

机器 ID 怎么分配:配置中心统一分配,或者用 IP 哈希。

序列号溢出怎么办:同一毫秒序列号用完,等下一毫秒。

雪花算法 ID 长度:64 位 long 型,MySQL bigint 能存。

为什么不用 UUID 做主键:无序,InnoDB 聚簇索引插入性能差,而且太长。

号段模式例子:数据库表存 max_id,应用一次取 1000 个,用完再取。

Redis ID 缺点:要持久化,不然重启丢 ID。

号段模式优点:数据库压力小,一次取一段。

雪花算法优点:有序、高性能、不依赖外部组件。

-- 本地消息表示例(本示例基于 MySQL 8.0)
CREATE TABLE local_message (
  id BIGINT PRIMARY KEY,
  biz_id BIGINT,
  content VARCHAR(512),
  status TINYINT DEFAULT 0,  -- 0待发 1已发 2失败
  create_time DATETIME
);

分布式事务方案对比

分库分表后,一个事务跨多个库,本地事务搞不定,要用分布式事务。

| 方案 | 原理 | 优点 | 缺点 | 适用 |

|---|---|---|---|---|

| 2PC | 两阶段提交 | 强一致 | 性能差、阻塞 | 对一致性要求极高 |

| TCC | Try-Confirm-Cancel | 灵活 | 业务侵入大 | 资金类 |

| 本地消息表 | 本地事务+消息 | 最终一致 | 有延迟 | 大部分业务 |

| MQ 事务消息 | RocketMQ 事务消息 | 最终一致 | 依赖 MQ | 大部分业务 |

推荐:大部分业务用本地消息表或 MQ 事务消息,最终一致就行。

2PC 为什么不常用:协调者挂了参与者阻塞,性能差,生产很少用。

TCC 适用场景:资金类、库存类,对一致性要求高但又不想用 2PC。

本地消息表原理:本地事务写业务表+消息表,后台任务扫消息表发 MQ,消费方处理。

MQ 事务消息原理:RocketMQ 半消息,本地事务成功再提交消息,失败回滚。

2PC 流程:协调者问所有参与者能提交吗,都回复 yes 再统一提交,有一个 no 就回滚。

最终一致 vs 强一致:大部分业务最终一致就行,不用追求强一致。

本地消息表缺点:有延迟,几秒到几分钟。

TCC 缺点:业务要写三套代码(Try、Confirm、Cancel),侵入大。

2PC 缺点:同步阻塞,协调者单点,性能差。

本地消息表怎么实现:业务表和消息表在同一个本地事务里,后台任务扫消息表发 MQ。

消息消费失败怎么办:重试,重试多次进死信队列人工处理。

最终一致延迟:正常几秒,异常时几分钟到几小时。

TCC 适用场景:下单扣库存、转账,对一致性要求高。

2PC 为什么不生产用:性能差、阻塞、协调者单点。

本地消息表优点:简单可靠,大部分业务够用。

MQ 事务消息优点:不用自己写扫表任务,MQ 帮你做。

-- 游标分页示例(本示例基于 MySQL 8.0)
-- 替代 limit 10000,10,避免深分页性能差
SELECT * FROM orders
WHERE user_id = 123 AND id > 上次最后ID
ORDER BY id LIMIT 10;

跨分片查询方案

分库分表后,跨分片查询是个大问题。

| 方案 | 原理 | 优缺点 |

|---|---|---|

| 中间件路由 | SQL 发到对应分片 | 透明,但不支持复杂 join |

| 业务层聚合 | 应用层查多个分片再聚合 | 灵活,但开发麻烦 |

| 冗余字段 | 把关联字段冗余到分片 | 避免 join,但数据冗余 |

| 宽表 | 一张大表存所有字段 | 查询简单,但更新麻烦 |

建议:尽量让查询都落在一个分片,避免跨分片。

跨分片分页:每个分片查 limit n 条,再归并排序取前 n,性能差。解决:游标分页。

跨分片 count:每个分片 count 再相加,不能精确统计。解决:维护计数表。

跨分片排序:每个分片排序,再归并排序,性能差。解决:尽量用分片键过滤。

跨分片 in 查询:in 条件展开到每个分片,结果归并。

宽表是什么:把关联表的字段冗余到一张大表,避免 join。

冗余字段风险:冗余字段更新要同步,不然数据不一致。

跨分片 sum:每个分片 sum 再相加,结果正确。

跨分片 max/min:每个分片 max 再取 max,结果正确。

跨分片 avg:每个分片 sum 和 count 都查,再算 avg。

跨分片 order by:每个分片排序,再归并排序,性能差。

游标分页是什么:where id > last_id limit n,每次传上次最后一个 id。

为什么游标分页好:不用 offset,直接定位,性能稳定。

宽表缺点:字段冗余多,更新麻烦。

冗余字段缺点:要同步更新,不然数据不一致。

跨分片 sum 怎么算:每个分片 sum 再相加。

分库分表中间件对比

| 中间件 | 原理 | 优点 | 缺点 |

|---|---|---|---|

| ShardingSphere-JDBC | JDBC 层增强 | 轻量、无代理 | 只支持 Java |

| ShardingSphere-Proxy | 代理层 | 多语言支持 | 多一层网络 |

| MyCAT | 代理层 | 功能全 | 性能一般 |

| Vitess | Google 开源 | 成熟 | 复杂 |

推荐:Java 项目用 ShardingSphere-JDBC,轻量无侵入。

ShardingSphere-JDBC 原理:JDBC 驱动层增强,应用直接连分片,无代理,性能好。

ShardingSphere-Proxy 原理:中间代理,应用连 Proxy,Proxy 再连分片,多语言支持。

MyCAT 特点:老牌中间件,功能全,但社区不活跃了,新项目不推荐。

Vitess 特点:Google 开源,YouTube 在用,成熟但部署复杂。

中间件做什么:SQL 解析、路由到正确分片、结果归并。

中间件性能损耗:JDBC 层几乎无损耗,Proxy 层多一跳网络,有损耗。

中间件缺点:SQL 支持有限,复杂 SQL 可能不支持。

什么时候不用中间件:分片规则简单,业务层自己路由就行。

ShardingSphere 生态:JDBC + Proxy,还有治理中心。

分库分表中间件支持读写分离吗:支持,ShardingSphere 自带读写分离。

中间件支持分布式事务吗:支持 Seata 集成,最终一致。

中间件运维成本:多一个组件,多一层故障点。

ShardingSphere-JDBC 缺点:只支持 Java,和应用绑死。

ShardingSphere-Proxy 缺点:多一跳网络,有性能损耗。

分库分表常见问题

分库分表不是银弹,会带来很多问题。

| 问题 | 说明 | 解决 |

|---|---|---|

| 跨分片 join | 关联查询跨库 | 冗余字段、宽表 |

| 分布式事务 | 跨库事务 | 最终一致方案 |

| 分布式 ID | 主键不重复 | 雪花算法 |

| 扩容 | 加分片要迁移数据 | 一致性哈希、预留分片 |

| 分页 | 跨分片分页 | 归并排序、禁止深分页 |

| 分布式 ID | 主键不重复 | 雪花算法 |

| 主键冲突 | 自增主键冲突 | 分布式 ID |

| 扩容迁移 | 数据迁移麻烦 | 双写迁移 |

| 运维复杂 | 分片多了难管理 | 中间件管理 |

| 跨库事务 | 跨库事务不支持 | 最终一致 |

| 全局唯一 | 全局唯一 ID | 分布式 ID |

| 扩容迁移 | 数据迁移不停机 | 双写+切读 |

| 跨库 join | 关联查询跨库 | 冗余字段 |

| 跨库事务 | 跨库事务不支持 | 最终一致 |

| 全局排序 | 跨分片排序慢 | 尽量分片键过滤 |

| 全局 count | 跨分片 count 不准 | 维护计数表 |

| 全局唯一约束 | 跨库唯一约束不支持 | 业务层保证 |

| 跨库分页 | 跨分片分页慢 | 游标分页 |

| 跨库排序 | 跨分片排序慢 | 分片键过滤 |

面试高频问答

分库分表面试高频题,每题一句话答案。

| 问题 | 一句话答案 |

|---|---|

| 什么时候分库分表 | 单表千万级、单库性能瓶颈 |

| 分片键怎么选 | 高频查询字段 |

| 分库分表后怎么做 join | 冗余字段 |

| 分布式 ID 用什么 | 雪花算法 |

| 分布式事务怎么做 | 最终一致 |

| 分布式 ID 用什么 | 雪花算法 |

| 分布式事务怎么做 | 最终一致,本地消息表 |

| 跨分片 join 怎么办 | 冗余字段、宽表 |

| 分库分表后主键怎么办 | 雪花算法 |

| 分库分表中间件选什么 | ShardingSphere-JDBC |

| 分布式事务用什么 | 本地消息表最终一致 |

| 分库分表怎么扩容 | 一致性哈希、双写迁移 |

| 雪花算法有什么问题 | 时钟回拨 |

| 什么是 TCC | Try-Confirm-Cancel |

| 分库分表什么时候用 | 单表千万级 |

| 分片键选什么 | 高频查询字段 |

| 分库分表后怎么做 join | 冗余字段 |

| 分布式 ID 选什么 | 雪花算法 |

| 分库分表中间件选什么 | ShardingSphere |

| 分布式事务怎么做 | 最终一致 |

| 分库分表后怎么做分页 | 游标分页 |

| 雪花算法时钟回拨怎么办 | 等或报错 |

| 一致性哈希是什么 | 哈希环 |

| 分库分表后怎么做扩容 | 双写迁移 |

| 分库分表什么时候用 | 单表千万级 |

| 分片键怎么选 | 高频查询字段 |

| 分库分表后怎么做 join | 冗余字段 |

| 分布式 ID 用什么 | 雪花算法 |

| 分布式事务怎么做 | 最终一致 |

自测

下面 10 道题覆盖分库分表核心考点,先自己想答案,再展开参考答案。

题 1:什么时候要分库分表?

<details>

<summary>参考答案</summary>

单表数据量超过千万级,或者单库 QPS 到瓶颈了。一般经验:单表 500 万行就要考虑,千万级必须分。

</details>

题 2:垂直拆分和水平拆分区别?

<details>

<summary>参考答案</summary>

垂直拆分按业务拆库、按字段拆表;水平拆分按数据行拆,把一张表的数据拆到多个库或多个表。

</details>

题 3:分片键怎么选?

<details>

<summary>参考答案</summary>

选高频查询字段,尽量让查询都落在一个分片。不能选会更新的字段,不然分片键变了数据要迁移。

</details>

题 4:哈希分片和范围分片优缺点?

<details>

<summary>参考答案</summary>

哈希分片数据均匀,但扩容要迁移大量数据;范围分片扩容简单,但容易热点。

</details>

题 5:分布式 ID 用什么方案?

<details>

<summary>参考答案</summary>

推荐雪花算法,时间戳+机器 ID+序列号,有序、高性能、不依赖外部组件。注意时钟回拨问题。

</details>

题 6:分布式事务有哪些方案?

<details>

<summary>参考答案</summary>

2PC 强一致但性能差;TCC 灵活但业务侵入大;本地消息表和 MQ 事务消息是最终一致,大部分业务够用。

</details>

题 7:跨分片 join 怎么办?

<details>

<summary>参考答案</summary>

尽量让 join 的表在同一个分片;实在不行就冗余字段、做宽表;中间件归并性能差,不推荐。

</details>

题 8:分库分表后怎么扩容?

<details>

<summary>参考答案</summary>

一致性哈希扩容迁移少;预留分片法,提前多分几个分片;双写迁移,新老分片同时写,再切读。

</details>

题 9:深分页在分库分表下有什么问题?

<details>

<summary>参考答案</summary>

每个分片都要查 limit 10000,10,再归并排序,性能极差。解决:用游标分页,或者禁止深分页。

</details>

题 10:分库分表中间件怎么选?

<details>

<summary>参考答案</summary>

Java 项目用 ShardingSphere-JDBC,轻量无代理;多语言用 ShardingSphere-Proxy。

</details>

题 11:分库分表后怎么扩容?

<details>

<summary>参考答案</summary>

一致性哈希扩容迁移少;预留分片法提前多分几个分片;双写迁移,新老分片同时写,再切读。

</details>

题 12:什么是 TCC?

<details>

<summary>参考答案</summary>

Try 预留资源、Confirm 确认提交、Cancel 取消回滚。业务侵入大,适合资金类强一致场景。

</details>

题 13:分库分表后怎么做分页?

<details>

<summary>参考答案</summary>

跨分片分页性能差,每个分片都要查 limit n 再归并。解决:游标分页(where id > last_id limit n),或者禁止深分页。

</details>

题 14:什么是一致性哈希?

<details>

<summary>参考答案</summary>

把哈希值组织成环,节点和数据都在环上,顺时针找最近的节点。扩容只迁移相邻节点的数据,比取模迁移少。

</details>

题 15:分库分表后怎么做分布式事务?

<details>

<summary>参考答案</summary>

大部分业务用本地消息表或 MQ 事务消息,最终一致。资金类强一致用 TCC。不用 2PC,性能差。

</details>

题 16:分库分表后怎么做分页?

<details>

<summary>参考答案</summary>

跨分片分页性能差,每个分片都要查 limit n 再归并。解决:游标分页(where id > last_id limit n),或者禁止深分页。

</details>

题 17:分库分表后怎么做 join?

<details>

<summary>参考答案</summary>

尽量让 join 的表在同一个分片;实在不行就冗余字段、做宽表;中间件归并性能差,不推荐。

</details>

题 18:什么是 TCC?

<details>

<summary>参考答案</summary>

Try 预留资源、Confirm 确认提交、Cancel 取消回滚。业务侵入大,适合资金类强一致场景。

</details>

题 19:分库分表后怎么扩容?

<details>

<summary>参考答案</summary>

一致性哈希扩容迁移少;预留分片法提前多分几个分片;双写迁移,新老分片同时写,再切读。

</details>

自测评分:19 道题答对 16 道以上,分库分表这块面试基本稳了;答对 12~15 道,建议把分片策略和分布式事务再看一遍;答对 12 道以下,先把垂直水平拆分区别背熟。


使用建议:分库分表是大招,不到万不得已别用。能加索引、能读写分离解决的问题,就别分库分表。

分库分表前先问自己:数据量真的到瓶颈了吗?加索引能不能解决?读写分离能不能扛?归档冷热数据能不能行?

面试答题技巧:先说什么时候该分,再说怎么分(分片键选什么),最后说带来什么问题(跨分片、分布式事务),怎么解决。

线上经验:分库分表后运维成本翻倍,小公司别盲目跟风。

分库分表后怎么查全表数据:业务层聚合所有分片结果。

分库分表后怎么做报表:用大数据组件,不要在 MySQL 里跑。

分库分表后怎么做数据同步:用 binlog 同步到数仓,不要业务代码双写。

分库分表后怎么做数据备份:每个分片单独备份,不要全库备份。

下一篇预告:第 81 章 速查卡:参数与调优

— 全文完 —回到顶部 ↑
下载推广海报

文章推广海报

《第 80 章 速查卡:分库分表与分布式》完整推广海报
DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。