0

0

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

狼影

狼影

发布时间:2025-09-11 08:52:01

|

235人浏览过

|

来源于php中文网

原创

使用 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
标志)或额外的表来显式标记双向关系。我个人强烈反对这种冗余设计。

为什么呢?

VWO
VWO

一个A/B测试工具

下载

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

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

相关专题

更多
数据分析工具有哪些
数据分析工具有哪些

数据分析工具有Excel、SQL、Python、R、Tableau、Power BI、SAS、SPSS和MATLAB等。详细介绍:1、Excel,具有强大的计算和数据处理功能;2、SQL,可以进行数据查询、过滤、排序、聚合等操作;3、Python,拥有丰富的数据分析库;4、R,拥有丰富的统计分析库和图形库;5、Tableau,提供了直观易用的用户界面等等。

683

2023.10.12

SQL中distinct的用法
SQL中distinct的用法

SQL中distinct的语法是“SELECT DISTINCT column1, column2,...,FROM table_name;”。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

323

2023.10.27

SQL中months_between使用方法
SQL中months_between使用方法

在SQL中,MONTHS_BETWEEN 是一个常见的函数,用于计算两个日期之间的月份差。想了解更多SQL的相关内容,可以阅读本专题下面的文章。

348

2024.02.23

SQL出现5120错误解决方法
SQL出现5120错误解决方法

SQL Server错误5120是由于没有足够的权限来访问或操作指定的数据库或文件引起的。想了解更多sql错误的相关内容,可以阅读本专题下面的文章。

1096

2024.03.06

sql procedure语法错误解决方法
sql procedure语法错误解决方法

sql procedure语法错误解决办法:1、仔细检查错误消息;2、检查语法规则;3、检查括号和引号;4、检查变量和参数;5、检查关键字和函数;6、逐步调试;7、参考文档和示例。想了解更多语法错误的相关内容,可以阅读本专题下面的文章。

358

2024.03.06

oracle数据库运行sql方法
oracle数据库运行sql方法

运行sql步骤包括:打开sql plus工具并连接到数据库。在提示符下输入sql语句。按enter键运行该语句。查看结果,错误消息或退出sql plus。想了解更多oracle数据库的相关内容,可以阅读本专题下面的文章。

697

2024.04.07

sql中where的含义
sql中where的含义

sql中where子句用于从表中过滤数据,它基于指定条件选择特定的行。想了解更多where的相关内容,可以阅读本专题下面的文章。

577

2024.04.29

sql中删除表的语句是什么
sql中删除表的语句是什么

sql中用于删除表的语句是drop table。语法为drop table table_name;该语句将永久删除指定表的表和数据。想了解更多sql的相关内容,可以阅读本专题下面的文章。

419

2024.04.29

html编辑相关教程合集
html编辑相关教程合集

本专题整合了html编辑相关教程合集,阅读专题下面的文章了解更多详细内容。

38

2026.01.21

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
MySQL 教程
MySQL 教程

共48课时 | 1.9万人学习

MySQL 初学入门(mosh老师)
MySQL 初学入门(mosh老师)

共3课时 | 0.3万人学习

简单聊聊mysql8与网络通信
简单聊聊mysql8与网络通信

共1课时 | 805人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号