单向记录就够了。关注是「用户A关注用户B」的有向动作,反向关系可查询得出;双向写入易导致不一致和脏数据;主键应为(follower_id, followee_id)联合主键并加followee_id索引;初期勿加status字段;删除用DELETE而非软删。

关注关系必须用双向记录还是单向记录
单向记录就够了。关注本质是「用户A关注用户B」这一有向动作,不需要冗余存「用户B被用户A关注」的反向记录——后者完全可以通过查询反向条件得到。强行双向写入会增加写操作复杂度,且极易因异常导致数据不一致。
常见错误现象:INSERT INTO follow (follower_id, followee_id) VALUES (1,2), (2,1) 以为这是“互相关注”,其实只是两条独立的单向关系;后续删关注时若只删一条,就会残留脏数据。
- 只建一条记录表示一个关注动作,
follower_id是主动关注者,followee_id是被关注者 - 查「谁关注了我」:用
WHERE followee_id = ? - 查「我关注了谁」:用
WHERE follower_id = ? - 互相关注判断:两次查询后取交集,或用
EXISTS子查询,不要依赖表里存在两条对称记录
主键和索引怎么设才不拖慢查询
主键必须是联合主键 (follower_id, followee_id),而不是自增ID。否则无法防止重复关注(同一对用户多次插入),也浪费唯一性约束能力。
性能影响很直接:没有合适索引时,查「我关注的人是否也关注我」这种场景要全表扫描。而加错索引(比如只对 follower_id 单独建索引)会导致 followee_id 条件无法走索引。
- 主键定义为
PRIMARY KEY (follower_id, followee_id) - 额外建一个二级索引:
INDEX idx_followee (followee_id, follower_id),支撑「查粉丝列表」场景 - 避免用
INT AUTO_INCREMENT主键 + 唯一索引组合,多一次索引维护开销,还容易在高并发下出现唯一键冲突重试
要不要加状态字段支持“已关注但未通过”这类需求
初期不要加。95% 的社交产品起步阶段的关注都是即时生效的,加 status 字段(如 pending/accepted)会把简单关系变成状态机,连带影响所有查询逻辑、通知触发点和前端判断分支。
错误使用场景:有人一上来就设计 status TINYINT,结果发现关注按钮点击后要等审核,但粉丝数统计、动态流过滤、@ 提示全都得适配三种状态,开发节奏立刻卡住。
- 纯公开关注(微博模式):不需要
status,删掉字段,简化一切 - 仅当明确需要私密关注(Instagram 私人账号)或申请制(知识星球类)时,再增加
status和对应的状态迁移逻辑 - 如果真要加,别用数字枚举,用
ENUM('pending','accepted','rejected')或VARCHAR(16),方便后期加新状态
删除关注时用 DELETE 还是软删
用 DELETE。关注关系天然可重建、无业务意义的历史痕迹,软删只会让表持续膨胀,索引碎片化,而且「取关后再关注」本就是新关系,没必要保留旧记录。
容易踩的坑:为“防止误操作”加 is_deleted,结果没配套清理逻辑,半年后表里 70% 记录是软删状态,COUNT(*) 统计失真,ORDER BY created_at LIMIT 20 查出来的全是无效数据。
- 删关注直接执行
DELETE FROM follow WHERE follower_id = ? AND followee_id = ? - 如果真需要审计,单独记日志表(
follow_log),不在主表上混存业务态和操作态 - 注意外键约束:如果关联了通知表或计数缓存表,删前先清理或用触发器/事务保证一致性
user_id 缺少索引,导致发通知变慢,进而拖垮整个关注接口。


















