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

来源:这里教程网 时间:2026-02-28 20:45:45 作者:

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

直接建

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

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

friendships
表,至少包含:
id
user_id
target_id
status
created_at
。其中:
status
推荐用 tinyint 或 enum,值设为
0=pending
1=accepted
2=rejected
3=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 个备用状态位。

相关推荐