设计题专项
面试官最爱出「你来设计一个 X」。你不是背模板的人--你是真的从 0 设计过一套库的人。
学 · 20 min
01设计题答题模板:需求澄清 -> 实体与关系 -> 建表 DDL -> 索引 -> 典型查询 -> 扩展性风险
场景面试官最爱出「你来设计一个 X」:点赞、私信、评论树……你不是背模板的人,但结构能让思路不漏项。
六步走:① 需求澄清:主动问「数据量级?读写比例?」--多数人上来就写表,这一问就是加分;② 实体与关系:画出有哪些对象、几对几;③ 建表 DDL:主键、类型、约束(W5 的功夫);④ 索引:对着典型查询建,说出理由(W6 的功夫);⑤ 典型查询:给三个高频查询的 SQL;⑥ 扩展性风险:数据量再大 10 倍会先崩哪里。每一步都是这八周练过的东西,拼起来而已。
-- 模板示例:点赞系统 create table likes ( user_id uuid not null, target_type text not null, -- post / comment target_id uuid not null, created_at timestamp not null default now(), primary key (user_id, target_type, target_id) -- 天然幂等 ); -- 典型查询:① 某内容的点赞数 ② 用户是否点过 ③ 我点赞过的列表 -- 扩展性:热点行的计数竞争 -> 计数表 / 缓存(W7 D48 的思路)易错写完 DDL 就停 = 只完成了三分之一;索引、典型查询、扩展性风险各占一分。
练 · 85 min · 每题 17 分钟
- 设计点赞系统(注意热点行与计数)
参考答案
要点:复合主键 (user_id, target_type, target_id) 天然幂等,重复点赞插不进去,不用先查再插;点赞数单独做计数表或缓存,别每次实时 count;三个典型查询:某内容的点赞数、我点过没有(主键直接命中)、我点赞过的列表(按 user_id 开头的索引,需要和主键顺序权衡);扩展性风险:热点内容的计数行竞争 -> 计数分桶 / 走缓存(D48 的思路)。DDL 参考上方学栏示例。
- 设计订单表与状态流转(状态机怎么存)
参考答案
CHECK 约束把非法状态挡在写入前(W5 的功夫);流转的并发安全靠一个 WHERE 同时完成校验与加锁。再配一张流转审计表(谁、何时、从什么状态改成什么状态),面试时点出「状态机 + 审计」就是做过的人。
-- 状态机怎么存:status + CHECK 锁住合法值;流转安全靠「带旧状态的 UPDATE」 create table orders ( id uuid primary key default gen_random_uuid(), user_id uuid not null, status smallint not null check (status in (1, 2, 3)), -- 1待支付 2已支付 3已取消 total_amount numeric(10,2) not null check (total_amount >= 0), created_at timestamp not null, paid_at timestamp, constraint paid_only_when_paid check (status <> 2 or paid_at is not null) ); -- 状态流转用 CAS 写法:WHERE 带上旧状态,改不到就是已被别人改过(:id 为参数占位) update orders set status = 2, paid_at = now() where id = :id and status = 1; -- 影响行数 = 0 说明这单已不在「待支付」态 - 设计私信/消息表(会话维度还是消息维度)
参考答案
双表设计:conversations(双方 user_id、最后一条消息冗余)+ messages(conversation_id、sender_id、内容、created_at)。只留消息表也能查出会话(按双方聚合),但「会话列表」是高频查询,冗余一张会话表换读取性能--能说出这个取舍就是加分。messages 索引 (conversation_id, id desc) 支持倒序翻页;超大会话分页用游标(where id < ?)不用 offset。
- 设计标签系统(多对多,以及按标签筛选的索引)
参考答案
中间表 (target_id, tag_id) 复合主键防重复打标;两个方向各要一条索引:按内容查它的标签走 (target_id, tag_id)(主键即覆盖),按标签筛内容走 (tag_id, target_id)。「同时含 A 和 B 标签」用 intersect 或 group by + having count(distinct tag_id)。风险:热门标签的筛选结果集倾斜,量大时配物化计数。
- 设计评论树(邻接表 / 路径枚举 / 闭包表三选一并说明理由)
参考答案
三方案各一句话:邻接表(parent_id)写入最简单、查整棵子树要递归 CTE;路径枚举(path 列)一条前缀 like 查子树,但移动节点要重写整棵子树的 path;闭包表(ancestor, descendant, depth)任意层级查询全能,代价是写入行数按子树膨胀。两级评论选邻接表最省;无限层级且高频查子树才上闭包表--选哪个不扣分,说得出理由才得分。