最稳妥的设计是单张表存follow_id和followed_id,加联合唯一索引uniq_follower_followed(follow_id,followed_id),并为followed_id单独建索引;tp6中用db::insert/delete配合事务,关注前查重,取关时严格校验参数;互关状态用redis缓存并及时双删。

关注关系表怎么设计才不翻车
直接用单张表存 follow_id(关注者)和 followed_id(被关注者)是最稳妥的,别搞双向冗余。很多人一开始想加个 status 字段做“待验证关注”,结果后期查询全乱——实际场景里 99% 的关注都是即时生效,status 只会增加 JOIN 和索引负担。
必须加联合唯一索引:UNIQUE KEY `uniq_follower_followed` (`follow_id`,`followed_id`),否则同一人点十次关注就插入十条重复记录。别信“前端防重”,后端没约束迟早出事。
常见错误现象:Duplicate entry '1001-2002' for key 'uniq_follower_followed' 就是漏建这个索引;查某人粉丝数却返回 0,往往是把 follow_id 和 followed_id 字段逻辑写反了。
TP6 的关注/取关操作怎么写才安全
别手写 SQL 拼接,用 Db::insert() 和 Db::delete() 配合事务。尤其取关动作,必须先查再删,避免误删他人关系——比如用户 A 点击“取消关注 B”,但请求参数被篡改传了 followed_id=9999,没校验就直接删,A 的所有关注关系可能全崩。
- 关注前先
Db::table('user_follow')->where(['follow_id' => $uid, 'followed_id' => $target_id])->find()判重 - 取关时用
Db::table('user_follow')->where(['follow_id' => $uid, 'followed_id' => $target_id])->delete(),别只传follow_id - 批量关注(如导入关注列表)必须用
Db::insertAll(),且每条数据都检查follow_id ≠ followed_id
查“我关注了谁”和“谁关注了我”为什么性能差
两个查询本质都是单字段索引扫描,但默认只对 follow_id 建了索引,查“谁关注了我”(即 followed_id = ?)就会全表扫。必须补上 INDEX idx_followed_id (followed_id)。
ThinkPHP 中用 withCount() 查粉丝数时,如果关联模型没指定 foreignKey,会生成错误的 ON 条件,导致结果为 0。正确写法:
$user->withCount(['fans' => function ($query) {
$query->where('followed_id', $user->id);
}])->find();
更推荐直接用原生子查询或视图缓存,withCount 在高并发下容易拖垮数据库。
互相关注状态怎么实时判断又不压数据库
每次渲染头像旁显示“已互关”都要查两次:一次查自己是否关注对方,一次查对方是否关注自己。高频页面(如动态流)扛不住。解决方案是加一个临时内存标记——用 Cache::store('redis')->get('mutual_'.$uid.'_'.$target_id) 缓存布尔值,过期时间设 60 秒即可。
更新时机很关键:任一用户取关时,必须同时删掉两条缓存:mutual_1001_2002 和 mutual_2002_1001。漏删一条,下次打开页面就显示错误状态。
别用数据库字段存“是否互关”,这属于派生状态,永远滞后且难维护。只要基础关注关系表干净、索引到位、缓存更新及时,互关判断就是毫秒级的事。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











