设计题专项

面试官最爱出「你来设计一个 X」。你不是背模板的人--你是真的从 0 设计过一套库的人。

学 20 min
练 85 min
盘 15 min
共 120 分钟

学 · 20 min

  1. 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 就停 = 只完成了三分之一;索引、典型查询、扩展性风险各占一分。

  2. 02一定要主动问「数据量级」和「读写比例」再动手

练 · 85 min · 每题 17 分钟

  1. 设计点赞系统(注意热点行与计数)
    参考答案

    要点:复合主键 (user_id, target_type, target_id) 天然幂等,重复点赞插不进去,不用先查再插;点赞数单独做计数表或缓存,别每次实时 count;三个典型查询:某内容的点赞数、我点过没有(主键直接命中)、我点赞过的列表(按 user_id 开头的索引,需要和主键顺序权衡);扩展性风险:热点内容的计数行竞争 -> 计数分桶 / 走缓存(D48 的思路)。DDL 参考上方学栏示例。

  2. 设计订单表与状态流转(状态机怎么存)
    参考答案

    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 说明这单已不在「待支付」态
  3. 设计私信/消息表(会话维度还是消息维度)
    参考答案

    双表设计:conversations(双方 user_id、最后一条消息冗余)+ messages(conversation_id、sender_id、内容、created_at)。只留消息表也能查出会话(按双方聚合),但「会话列表」是高频查询,冗余一张会话表换读取性能--能说出这个取舍就是加分。messages 索引 (conversation_id, id desc) 支持倒序翻页;超大会话分页用游标(where id < ?)不用 offset。

  4. 设计标签系统(多对多,以及按标签筛选的索引)
    参考答案

    中间表 (target_id, tag_id) 复合主键防重复打标;两个方向各要一条索引:按内容查它的标签走 (target_id, tag_id)(主键即覆盖),按标签筛内容走 (tag_id, target_id)。「同时含 A 和 B 标签」用 intersect 或 group by + having count(distinct tag_id)。风险:热门标签的筛选结果集倾斜,量大时配物化计数。

  5. 设计评论树(邻接表 / 路径枚举 / 闭包表三选一并说明理由)
    参考答案

    三方案各一句话:邻接表(parent_id)写入最简单、查整棵子树要递归 CTE;路径枚举(path 列)一条前缀 like 查子树,但移动节点要重写整棵子树的 path;闭包表(ancestor, descendant, depth)任意层级查询全能,代价是写入行数按子树膨胀。两级评论选邻接表最省;无限层级且高频查子树才上闭包表--选哪个不扣分,说得出理由才得分。

过关标准 5 题都给出了 DDL + 索引 + 3 个典型查询 + 1 个扩展性风险。