必须建user_favorites表和被收藏的目标表(如articles);user_favorites需含id、user_id、article_id、created_at字段,并加(user_id, article_id)联合唯一索引及(user_id, created_at)联合索引。

收藏功能必须建哪几张表
ThinkPHP 本身不提供收藏功能的内置模型,得自己设计数据结构。核心就两张表:user_favorites(用户收藏关系表)和被收藏的目标表(比如 articles、products)。不要试图用一个泛型字段(如 target_type + target_id)硬撑所有类型——后期查收藏列表、统计、联表排序都会变慢,且容易漏加索引。
推荐做法是按业务收敛:如果只收藏文章,就建 user_favorites 表,字段至少包含:id、user_id、article_id、created_at;加上联合唯一索引 (user_id, article_id),防止重复收藏。
常见错误现象:INSERT INTO user_favorites (user_id, article_id) VALUES (?, ?) 不加唯一约束,导致同个用户对同篇文章存多条;查“我收藏了没”时用 count() 而不是 exists(),拖慢响应。
如何判断当前用户是否已收藏某条记录
别写 Db::name('user_favorites')->where(['user_id' => $uid, 'article_id' => $aid])->count() > 0。count 查询会扫全量匹配行,哪怕只想要“有还是没有”。直接用 exists() 或 find() 更轻量。
立即学习“PHP免费学习笔记(深入)”;
// 推荐:用 find() 拿一条即可
$hasFav = Db::name('user_favorites')
->where(['user_id' => $uid, 'article_id' => $aid])
->find() !== null;
<p>// 或更明确的 exists(TP6.1+)
$hasFav = Db::name('user_favorites')
->where(['user_id' => $uid, 'article_id' => $aid])
->exists();</p>
注意点:$uid 必须是登录态验证后的用户 ID,不能直接取 input('user_id');$aid 要做过滤(比如 intval() 或绑定参数),否则 SQL 注入风险明显。
收藏/取消收藏的原子操作怎么写才安全
前端点一次“收藏”,后端可能并发触发多次请求。单纯先查再删/插,必然出现竞态:两个请求同时查到“未收藏”,然后都执行插入,破掉唯一索引或导致重复。
正确做法是靠数据库层兜底:
- 插入用
INSERT IGNORE(MySQL)或ON DUPLICATE KEY UPDATE,失败不报错,返回影响行数判断结果 - 删除用
DELETE WHERE user_id = ? AND article_id = ?,看affectedRows是否为 1 - TP6 可用
Db::execute()手动发原生语句,或封装成事务(但单条 DML 一般不需要)
示例(收藏动作):
<pre class="brush:php;toolbar:false;">$result = Db::execute(
'INSERT IGNORE INTO user_favorites (user_id, article_id, created_at) VALUES (?, ?, NOW())',
[$uid, $aid]
);
$isAdded = $result === 1;
用户收藏列表分页查询为什么越来越慢
当 user_favorites 表数据过百万,<code>SELECT * FROM user_favorites WHERE user_id = ? ORDER BY created_at DESC LIMIT 20 会变慢——因为没对 user_id + created_at 建联合索引。
必须加:ALTER TABLE user_favorites ADD INDEX idx_user_time (user_id, created_at DESC);(MySQL 8.0+ 支持降序索引;低版本用 created_at 升序,查时 ORDER BY created_at DESC 仍可用索引)。
另一个坑:别在收藏列表里直接 JOIN articles 查标题、封面等——文章表若没走好索引,或字段太多,会导致 EXPLAIN 显示 Using temporary; Using filesort。拆成两步:先查收藏 ID 列表,再用 WHERE id IN (...) 拉详情,更可控。



















