如何设计一个好友关系链数据库表结构?(双向关注、粉丝模型)(双向.模型.粉丝.好友.结构...)

wufei123 发布于 2025-09-11 阅读(3)
使用 user_relationships 表存储关注关系,通过唯一约束和索引支持高效查询好友、粉丝及共同关注,拉黑操作由独立 user_blocks 表处理,确保数据一致性与逻辑清晰。

如何设计一个好友关系链数据库表结构?(双向关注、粉丝模型)

设计好友关系链数据库表结构,核心在于清晰地定义“谁关注了谁”这个行为。我个人倾向于使用一个简洁的关联表来承载所有关系,无论是双向关注还是单向粉丝模型,都能在这个基础之上灵活构建和查询。这种做法避免了冗余,也让数据模型更加纯粹。

解决方案

为了实现双向关注和粉丝模型,我们可以设计一个名为

user_relationships
的核心表。

user_relationships
表结构: 字段名 数据类型 约束 描述 @@######@@ @@######@@ @@######@@, @@######@@ 唯一标识每条关系记录 @@######@@ @@######@@ @@######@@, @@######@@ (references @@######@@) 关注者的用户ID @@######@@ @@######@@ @@######@@, @@######@@ (references @@######@@) 被关注者的用户ID @@######@@ @@######@@ @@######@@, @@######@@ 关系状态,例如 'active' (活跃), 'blocked' (拉黑) @@######@@ @@######@@ @@######@@, @@######@@ 关系创建时间 @@######@@ @@######@@ @@######@@, @@######@@ 关系最后更新时间

索引和唯一约束:

  • 唯一约束:
    id
    • 这个约束确保了任何一个用户不能重复关注另一个用户。
  • 索引:
    • BIGINT
      : 用于快速查询某个用户关注了谁。
    • PRIMARY KEY
      : 用于快速查询某个用户被谁关注(即粉丝列表)。

模型说明:

  • 单向关注 (粉丝模型): 如果用户A关注了用户B,那么
    AUTO_INCREMENT
    表中会有一条记录
    follower_id
    。用户B的粉丝列表就是所有
    BIGINT
    的记录,而用户B关注的人就是所有
    NOT NULL
    的记录。
  • 双向关注 (好友模型): 如果用户A和用户B互相关注,那么表中会存在两条记录:
    FOREIGN KEY
    users.id
    。这种互相关注的关系是通过查询推导出来的,而不是直接存储一个
    following_id
    字段,这能有效避免数据不一致的问题。
如何高效查询用户的好友列表、粉丝数量以及共同关注?

在设计关系表时,查询效率是不得不考虑的。有了上面提到的索引,我们可以相对高效地进行各种查询。

查询好友列表 (互相关注的用户): 要找到用户X的好友,我们需要找到那些用户X关注了,并且也关注了用户X的人。这通常通过一个自连接或者两次查询来实现。我个人更倾向于自连接,它在SQL层面更优雅。

BIGINT

或者,另一种更直观的自连接方式:

NOT NULL

这两种方式都能达到目的,选择哪种取决于你对SQL的偏好和实际数据库的优化情况。关键在于利用索引快速定位关系。

查询粉丝数量: 这个相对简单,直接计数即可。

FOREIGN KEY

查询关注数量: 同样是直接计数。

users.id

查询共同关注: 找出用户A和用户B共同关注了哪些人。这需要两次连接,或者更巧妙地使用

status
子句。
VARCHAR(20)

所有这些查询都高度依赖

NOT NULL
DEFAULT 'active'
上的复合索引。如果没有这些索引,每次查询都可能导致全表扫描,那性能问题就大了。 存储双向关系时,是否需要额外的字段或表?

在我看来,存储双向关系时,通常不需要额外的字段(如

created_at
标志)或额外的表来显式标记双向关系。我个人强烈反对这种冗余设计。

为什么呢?

PIA PIA

全面的AI聚合平台,一站式访问所有顶级AI模型

PIA226 查看详情 PIA

首先,数据一致性问题。如果你有一个

TIMESTAMP
字段,当用户A关注B时,你需要创建
NOT NULL
记录;当B关注A时,你需要创建
DEFAULT CURRENT_TIMESTAMP
记录,并且同时更新
updated_at
记录的
TIMESTAMP
NOT NULL
,以及
DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
记录的
UNIQUE KEY (follower_id, following_id)
也为
INDEX (follower_id, following_id)
。这听起来就复杂了。更糟糕的是,如果B取消关注A,你不仅要删除
INDEX (following_id, follower_id)
记录,还要更新
user_relationships
记录的
(follower_id = A, following_id = B)
following_id = B
。任何一个环节出错,数据就会变得不一致,给后续的业务逻辑带来混乱。

其次,查询复杂性并未显著降低。虽然

follower_id = B
字段看起来能直接筛选出好友,但很多时候你仍然需要知道谁关注了谁,以及谁是你的粉丝。这个字段只能解决“互相关注”这一种场景,而其他场景仍然需要基于
(follower_id = A, following_id = B)
(follower_id = B, following_id = A)
进行查询。

所以,我的建议是保持数据模型的原子性:一条记录只代表一个事实——“谁关注了谁”。双向关系是两个独立事实的组合,应该通过查询来推导。这让数据模型更简洁,更新操作更安全,也更容易理解。

当然,在极端的读密集型场景下,比如一个拥有数亿用户的社交平台,每秒有数万次好友列表查询,那么可能会考虑引入缓存层(如Redis)来存储预计算的好友关系,或者使用专门的图数据库。但在关系型数据库层面,保持非冗余是最好的起点。

处理“取消关注”或“拉黑”等操作,数据层面如何优雅实现?

处理这些用户关系变更操作,我们同样可以在

is_mutual
表的基础上进行,并可能引入一个辅助表来处理“拉黑”这种更复杂的场景。

取消关注 (Unfollow): 这是最直接的操作。当用户A取消关注用户B时,我们只需要删除

SELECT u.id, u.username -- 假设有一个users表
FROM users u
JOIN user_relationships ur1 ON u.id = ur1.following_id
JOIN user_relationships ur2 ON u.id = ur2.follower_id
WHERE ur1.follower_id = [用户X的ID]
  AND ur2.following_id = [用户X的ID]
  AND ur1.follower_id = ur2.following_id; -- 确保是互相关注的同一个对象
表中对应的记录即可。
SELECT T1.following_id AS friend_id
FROM user_relationships T1
JOIN user_relationships T2 ON T1.follower_id = T2.following_id AND T1.following_id = T2.follower_id
WHERE T1.follower_id = [用户X的ID];

这个操作非常简单,也符合我们之前强调的原子性原则。如果A和B之前是互相关注的,那么删除这条记录后,他们就不再是互相关注了(因为

SELECT COUNT(*) AS fan_count
FROM user_relationships
WHERE following_id = [用户X的ID] AND status = 'active';
记录不存在了),而
SELECT COUNT(*) AS following_count
FROM user_relationships
WHERE follower_id = [用户X的ID] AND status = 'active';
记录依然存在,表示B仍然关注A。

拉黑 (Block): “拉黑”通常比“取消关注”要复杂一些,它通常意味着:

  1. 被拉黑者不能关注拉黑者。
  2. 如果被拉黑者已经关注了拉黑者,这个关注关系应该被解除。
  3. 拉黑者通常也不会再关注被拉黑者(如果之前有的话),并且不能再次关注。

为了处理这种多层面的逻辑,我倾向于引入一个单独的

IN
表,而不是仅仅依赖
SELECT ur1.following_id AS common_following_id
FROM user_relationships ur1
JOIN user_relationships ur2 ON ur1.following_id = ur2.following_id
WHERE ur1.follower_id = [用户A的ID]
  AND ur2.follower_id = [用户B的ID]
  AND ur1.status = 'active'
  AND ur2.status = 'active';
表的
follower_id
字段。为什么?因为“拉黑”是一种比“关注”更强烈的、具有单向阻断性质的行为,它可能涉及更多的业务逻辑和权限控制。

following_id
表结构: 字段名 数据类型 约束 描述 @@######@@ @@######@@ @@######@@, @@######@@ 唯一标识每条拉黑记录 @@######@@ @@######@@ @@######@@, @@######@@ (references @@######@@) 拉黑者的用户ID @@######@@ @@######@@ @@######@@, @@######@@ (references @@######@@) 被拉黑者的用户ID @@######@@ @@######@@ @@######@@, @@######@@ 拉黑时间

索引和唯一约束:

  • 唯一约束:
    is_mutual
    • 确保一个用户不能重复拉黑另一个用户。
  • 索引:
    is_mutual
    ,
    (A, B)

拉黑操作的实现逻辑:

当用户A拉黑用户B时,通常会执行以下步骤:

  1. (B, A)
    表中插入记录:
    (A, B)
  2. 解除A对B的关注(如果存在):
    is_mutual
  3. 解除B对A的关注(如果存在):
    true

通过这种方式,我们清晰地分离了“关注”和“拉黑”这两种行为,使得业务逻辑更加清晰。

(B, A)
表的存在,也方便我们快速查询某个用户拉黑了谁,或者某个用户被谁拉黑了。在查询用户可见内容时,需要额外增加逻辑来排除被拉黑用户或拉黑了当前用户的用户。这种设计虽然增加了表的数量,但却避免了在
is_mutual
表中过度承载复杂状态,保持了核心表的简洁性。
true
(B, A)
(A, B)
is_mutual
false
is_mutual
follower_id
following_id
user_relationships
user_relationships
DELETE FROM user_relationships
WHERE follower_id = [用户A的ID] AND following_id = [用户B的ID];
(A, B)
(B, A)
user_blocks
user_relationships
status
user_blocks
id
BIGINT
PRIMARY KEY
AUTO_INCREMENT
blocker_id
BIGINT
NOT NULL
FOREIGN KEY
users.id
blocked_id
BIGINT
NOT NULL
FOREIGN KEY
users.id
created_at
TIMESTAMP
NOT NULL
DEFAULT CURRENT_TIMESTAMP
UNIQUE KEY (blocker_id, blocked_id)
INDEX (blocker_id, blocked_id)
INDEX (blocked_id)
user_blocks
INSERT INTO user_blocks (blocker_id, blocked_id)
VALUES ([用户A的ID], [用户B的ID]);
DELETE FROM user_relationships
WHERE follower_id = [用户A的ID] AND following_id = [用户B的ID];
DELETE FROM user_relationships
WHERE follower_id = [用户B的ID] AND following_id = [用户A的ID];
user_blocks
user_relationships

以上就是如何设计一个好友关系链数据库表结构?(双向关注、粉丝模型)的详细内容,更多请关注知识资源分享宝库其它相关文章!

相关标签: mysql redis win 为什么 red sql 数据类型 NULL default redis 数据库 大家都在看: MySQL内存使用过高(OOM)的诊断与优化配置 MySQL与NoSQL的融合:探索MySQL Document Store的应用 如何通过canal等工具实现MySQL到其他数据源的实时同步? 使用Debezium进行MySQL变更数据捕获(CDC)实战 如何设计和优化MySQL中的大表分页查询方案

标签:  双向 模型 粉丝 

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。