系统设计 023:三大核心表Sharding设计思路与最优解决方案
- ✨前言
- [Bilibili 同步视频](#Bilibili 同步视频)
- 一、分片设计核心底层逻辑
- [二、Session Table 会话表分片实战](#二、Session Table 会话表分片实战)
-
- [2.1 表结构与业务场景](#2.1 表结构与业务场景)
- [2.2 查询逻辑分析](#2.2 查询逻辑分析)
- [2.3 分片方案设计](#2.3 分片方案设计)
- [2.4 核心代码示例(分片路由逻辑)](#2.4 核心代码示例(分片路由逻辑))
- [2.5 方案优势总结](#2.5 方案优势总结)
- [三、Newsfeed&Timeline 社交动态表分片实战](#三、Newsfeed&Timeline 社交动态表分片实战)
-
- [3.1 业务定位区分](#3.1 业务定位区分)
- [3.2 分片逻辑拆解](#3.2 分片逻辑拆解)
- [✅ Newsfeed 分片方案](#✅ Newsfeed 分片方案)
- [✅ Timeline 分片方案](#✅ Timeline 分片方案)
- [3.3 核心查询SQL示例](#3.3 核心查询SQL示例)
- [3.4 方案设计价值](#3.4 方案设计价值)
- [四、Leetcode Submission 刷题提交记录表分片实战(重难点)](#四、Leetcode Submission 刷题提交记录表分片实战(重难点))
-
- [4.1 表业务与字段说明](#4.1 表业务与字段说明)
- [4.2 双向查询需求(核心冲突点)](#4.2 双向查询需求(核心冲突点))
- [4.3 单一分片键的致命缺陷](#4.3 单一分片键的致命缺陷)
- [4.4 最优解决方案:主副表双分片设计](#4.4 最优解决方案:主副表双分片设计)
- [1. 主表设计(submission_main)](#1. 主表设计(submission_main))
- [2. 辅助索引表设计(submission_index)](#2. 辅助索引表设计(submission_index))
- [4.5 双表查询逻辑代码示例](#4.5 双表查询逻辑代码示例)
- [4.6 方案核心优势分析](#4.6 方案核心优势分析)
- 五、全文核心总结与设计思维升华
✨前言
在分布式数据库架构设计中,**分库分表(Sharding)**是解决单表数据量过大、查询性能瓶颈、系统吞吐量受限的核心技术手段🔥。业界一直流传着一条黄金设计准则:数据库的分片规则,永远由业务查询逻辑决定。脱离查询场景的分片设计,无异于无源之水、无本之木,不仅无法优化性能,反而会引发跨库查询、数据倾斜、维护困难等一系列问题。
本文将结合三类高频实战数据表:Session会话表、Newsfeed动态流表、Timeline个人主页表、Leetcode刷题提交记录表,深度拆解不同业务场景下的分片设计逻辑、核心痛点与最优落地方案,搭配原理详解+实战代码+场景适配分析,全方位吃透分布式分片核心思维💡。
Bilibili 同步视频
一、分片设计核心底层逻辑
在正式进入实战案例前,我们先锚定分片设计的核心本质,所有分片方案均围绕以下逻辑展开:
-
核心原则 :Query 决定 Sharding,优先贴合高频查询维度设计分片键
-
设计目标:规避跨分片查询、减少全表扫描、均匀分散数据、提升读写性能
-
核心痛点:单分片键无法适配多维度查询场景,是分布式分片最常见的设计难题
二、Session Table 会话表分片实战
2.1 表结构与业务场景
Session会话表是后端系统的核心基础表,主要用于存储用户登录会话信息,维持用户在线状态,核心字段如下👇:
-
session_key:会话唯一标识(Session Token),全局唯一 -
user_id:关联用户ID,标记会话所属用户 -
expire_at:会话过期时间,用于清理失效会话数据 -
create_time:会话创建时间
业务场景 :用户登录系统后,浏览器会将 session_key 存储在本地Cookie中,后续每一次接口请求、页面访问,都会自动携带该Token完成身份校验。
2.2 查询逻辑分析
在真实业务场景中,Session表99%的查询均基于session_key 。用户请求身份校验时,后端仅能获取请求携带的 session_key,无法提前预知当前请求的 user_id,因此绝对无法通过用户ID作为查询条件。
2.3 分片方案设计
基于查询逻辑,Session表唯一最优分片键:session_key✅
通过 session_key进行哈希分片,可实现:每一条会话数据精准落在对应分片,身份校验查询仅需路由单个分片,零跨分片查询、极速响应。
2.4 核心代码示例(分片路由逻辑)
java
/**
* Session表分片路由工具类
* 分片规则:session_key哈希取模,固定路由分片
*/
public class SessionShardingUtil {
// 分片总数,可根据业务扩容调整
private static final int SHARDING_COUNT = 16;
/**
* 根据session_key获取目标分片
* @param sessionKey 会话唯一Token
* @return 目标分片编号
*/
public static int getSessionShardIndex(String sessionKey) {
// 哈希算法均匀打散数据,避免数据倾斜
int hash = Math.abs(sessionKey.hashCode());
return hash % SHARDING_COUNT;
}
}
2.5 方案优势总结
-
完美适配高频查询场景,单次身份校验查询耗时稳定在10ms以内⚡
-
session_key全局唯一,哈希分片可实现数据均匀分布,无热点分片
-
逻辑简单、维护成本低,适配高并发登录、鉴权场景
三、Newsfeed&Timeline 社交动态表分片实战
在社交、内容类系统中,Newsfeed和Timeline是两大核心动态展示模块,业务场景相近但定位不同,分片逻辑均围绕用户维度展开,是用户维度分片的经典落地案例📱。
3.1 业务定位区分
-
Newsfeed(信息流):用户关注好友、博主发布的所有内容汇总,是「别人的内容给自己看」,每个用户仅有权限查看属于自己的信息流
-
Timeline(个人主页):单个用户自主发布的所有动态、帖子汇总,是「自己的内容给别人/自己看」
3.2 分片逻辑拆解
✅ Newsfeed 分片方案
Newsfeed表核心关联字段为 owner_id(信息流所属用户ID)。业务查询逻辑固定为:用户登录后,通过自身user_id查询属于自己的信息流数据。
因此,Newsfeed表采用 owner_id 作为分片键,所有用户的信息流数据根据自身ID分片存储,查询时精准路由对应分片。
✅ Timeline 分片方案
Timeline数据源自用户发布的帖子主表(Post/Trend表),查询逻辑固定为:根据指定user_id,筛选该用户发布的全部动态。
因此,Timeline体系统一采用 user_id 作为分片键,贴合「查询单人所有数据」的高频场景。
3.3 核心查询SQL示例
sql
-- Newsfeed查询:获取当前用户的信息流
SELECT * FROM newsfeed_table
WHERE owner_id = #{current_user_id}
ORDER BY publish_time DESC LIMIT 20;
-- Timeline查询:获取指定用户的个人动态
SELECT * FROM post_table
WHERE user_id = #{target_user_id}
ORDER BY publish_time DESC LIMIT 20;
3.4 方案设计价值
社交类系统90%的内容查询均围绕「用户维度」展开,基于user_id/owner_id分片,可实现所有核心查询均命中单分片,彻底规避跨分片聚合查询的性能损耗,完美适配高并发刷动态、访问主页的业务场景。
四、Leetcode Submission 刷题提交记录表分片实战(重难点)
相较于前两类单维度查询场景,Leetcode刷题提交记录表是多维度查询冲突的典型场景,也是分布式分片设计的重难点案例,极具学习参考价值💻。
4.1 表业务与字段说明
Submission表用于存储用户刷题的所有提交记录,完整记录每一次代码提交的核心数据,核心字段包含:
-
user_id:刷题用户ID -
problem_id:题目唯一ID -
submit_time:提交时间戳 -
submit_code:提交代码内容(大体积文本) -
status:提交结果(通过/超时/报错等) -
code_length:代码长度
4.2 双向查询需求(核心冲突点)
该表存在两个高频且优先级对等的查询维度,也是分片设计的核心矛盾:
-
维度一:题目维度查询:查询某一道题目的所有用户提交记录,按时间、代码长度排序,用于展示题目排行榜、优秀题解
-
维度二:用户维度查询:查询某一个用户的所有提交记录,筛选刷题记录、通过题目、错题记录等个人数据
4.3 单一分片键的致命缺陷
若采用传统单分片键设计,无论选择哪个维度,都会出现性能瓶颈:
-
以
user_id为分片键:查询题目全量提交记录时,需遍历所有分片数据,全分片扫描性能极差 -
以
problem_id为分片键:查询用户个人刷题记录时,同样需要跨全分片聚合,无法适配高频个人中心查询
简言之:单一分片键无法同时满足双向高频查询需求,这也是分布式数据库多维度查询的通用痛点。
4.4 最优解决方案:主副表双分片设计
结合业务查询频次、存储成本、性能损耗,最终采用主表+辅助表的双分片架构,兼顾查询效率与存储性价比✅
1. 主表设计(submission_main)
分片键:user_id
原因:用户个人刷题记录查询为高频日常操作 ,优先级高于题目排行榜查询,优先保障核心高频场景性能。主表存储全量完整数据,包含代码、提交状态、时间等所有字段。
2. 辅助索引表设计(submission_index)
分片键:problem_id
核心设计思路:冗余筛选字段、舍弃大体积数据。辅助表仅存储用于查询、筛选、排序的轻量级字段,不存储大容量代码数据,大幅降低存储成本。
辅助表核心字段:problem_id、user_id、submit_time、status、code_length
4.5 双表查询逻辑代码示例
java
/**
* 刷题提交记录分片查询工具
* 主表:user_id分片,查询用户记录
* 辅助表:problem_id分片,查询题目记录
*/
public class SubmissionShardingUtil {
private static final int SHARDING_NUM = 16;
// 用户维度查询:路由主表
public static int getUserSubmissionShard(Long userId) {
return Math.abs(userId.hashCode()) % SHARDING_NUM;
}
// 题目维度查询:路由辅助索引表
public static int getProblemSubmissionShard(Long problemId) {
return Math.abs(problemId.hashCode()) % SHARDING_NUM;
}
}
4.6 方案核心优势分析
-
性能最优:双向查询均命中单分片,彻底杜绝全分片扫描,查询性能提升10倍以上⚡
-
成本可控:辅助表仅冗余轻量字段,舍弃大容量代码数据,存储冗余成本极低
-
场景全覆盖:同时适配用户个人记录、题目排行榜两大核心场景
-
适配SQL数据库特性:依托SQL数据库多索引能力,弥补分布式分片的维度短板
五、全文核心总结与设计思维升华
通过三类实战数据表的分片设计拆解,我们可以提炼出分布式数据库分片的通用设计思维,适用于90%的业务场景📝:
-
场景优先,Query驱动:所有分片规则不凭经验、不凭直觉,完全贴合业务高频查询逻辑,这是分片设计的第一准则
-
单维场景极简设计:单一查询维度场景,直接采用对应维度哈希分片,简洁高效、零冗余、低维护
-
多维场景冗余设计:多查询维度冲突时,拒绝单一分片键硬适配,采用「主表兜底+辅助表补全」的冗余方案,以极小的存储成本换取极致查询性能
-
冷热数据分层:大体积、低频使用数据不冗余,仅对筛选、排序核心字段做冗余存储,平衡性能与成本

分布式分片技术的核心从来不是复杂的算法与规则,而是贴合业务、取舍有度的架构思维。读懂业务查询场景,就掌握了分片设计的核心密码🔑。