mysql在社交网站中的好友关系数据库设计

2次阅读

单张friends表存user_id+friend_id会踩三个坑:关系方向模糊、重复插入难防、双向操作变复杂;应改用friendships表,含user_id、target_id、status等字段,并加联合唯一索引和覆盖索引优化查询。

mysql在社交网站中的好友关系数据库设计

为什么不能用单张表存 user_id + friend_id 就完事?

直接建 friends 表,字段为 user_idfriend_id,看起来简洁,但实际会踩三个坑:关系方向模糊、重复插入难防、双向操作变复杂。比如 A 加了 B 为好友,数据库里存了 (1, 2),那 B 是否也自动关注了 A?业务上通常要区分“我关注的人”和“关注我的人”,甚至还要支持“互相关注但状态不同”(如一方已验证、另一方待确认)。单向记录无法支撑这类扩展。

推荐结构:一张关系表 + 明确状态字段

friendships 表,至少包含:iduser_idtarget_idstatuscreated_at。其中:status 推荐用 tinyint 或 enum,值设为 0=pending1=accepted2=rejected3=blocked。关键点:

  • user_id 是发起方,target_id 是被操作用户,方向明确,查“A的关注列表”就是 WHERE user_id = A,查“A的粉丝”就是 WHERE target_id = A
  • 联合唯一索引必须加:UNIQUE KEY `ux_user_target` (`user_id`, `target_id`),防止重复申请
  • 如果要支持“双向好友”逻辑(即只有双方都 accepted 才算真正好友),不要在写入时硬编码双条记录,而是在查询层或定时任务中判断 WHERE (user_id = A AND target_id = B AND status = 1) AND (user_id = B AND target_id = A AND status = 1)

怎么高效查“共同好友”?别用 JOIN 套子套子

查用户 A 和用户 B 的共同好友,常见错误是写两层子查询或 INNER JOIN 两张 friendships 表——数据量一上去就慢。更稳的做法是用 IN + 覆盖索引:

SELECT target_id FROM friendships WHERE user_id = 123   AND status = 1   AND target_id IN (     SELECT target_id     FROM friendships     WHERE user_id = 456 AND status = 1   );

前提是 (user_id, status, target_id) 有联合索引,这样内层子查询能走索引下推,外层也能快速定位。如果并发高、实时性要求不强,共同好友结果完全可以缓存到 redis,键名如 common_friends:123:456,过期时间设为 1 小时。

删除好友时,到底该删一条还是两条记录?

答案是:只删一条,且必须明确删哪条。用户 A 主动取消关注 B,只需执行:delete FROM friendships WHERE user_id = A AND target_id = B。不要连带删 (B, A) 那条——因为那是 B 对 A 的独立关系,可能仍是“B 关注 A”或“B 拉黑 A”。误删会导致状态丢失。补充建议:

  • 加软删字段 is_deleted TINYINT default 0,配合定时归档,比物理删除更安全
  • 所有删除操作必须走事务,并同步更新内存缓存(如用户 A 的关注数减 1)
  • 如果业务允许“单向取关不通知”,那删完就结束;如果要通知对方(如“XX 取关了你”),得额外发消息,不能依赖数据库触发器——mysql 触发器没法可靠调外部服务

实际最难的不是建表,而是状态流转边界。比如“拉黑后能否再发好友申请”“被拉黑者是否还能看到对方主页”——这些都得靠 status 字段组合判断,而不是靠表结构本身。字段设计要留出至少 1–2 个备用状态位。

text=ZqhQzanResources