点赞和收藏需分开建表,因二者业务语义、查询频率与生命周期不同;混用会导致索引低效、权限难控、统计耦合;核心防重靠数据库唯一约束而非应用层判断。

点赞和收藏为什么要分开建表
因为点赞(like)和收藏(favorite)虽然行为相似,但业务语义、查询频率、生命周期都不同。混在一张表里会导致索引低效、权限难控制、统计逻辑耦合。比如用户取消点赞很频繁,但收藏删除率低;又比如「我收藏了哪些文章」要常查,而「谁点过这个评论」可能只在后台用。分开后,likes 表可以加 (user_id, target_type, target_id) 联合唯一索引防重复,favorites 表则更适合加 created_at 降序索引支持「最近收藏」。
如何避免重复点赞或重复收藏
核心是靠数据库层唯一约束,不是靠应用层判断。否则并发请求下容易写入两条。建表时必须声明:
CREATE TABLE likes ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, target_type VARCHAR(20) NOT NULL COMMENT 'post, comment, reply', target_id BIGINT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_target (user_id, target_type, target_id) );
执行 INSERT INTO likes (user_id, target_type, target_id) VALUES (123, 'post', 456) 时,如果已存在相同三元组,MySQL 直接报错 ERROR 1062 (23000): Duplicate entry '123-post-456' for key 'uk_user_target'。应用层捕获这个错误,返回「已点过赞」即可。
怎么高效查某篇文章的点赞数和是否被当前用户点过赞
不能每次查都 COUNT(*) 全表扫描,也不能用两个独立查询(N+1 问题)。推荐用单条 SQL + 条件聚合:
SELECT COUNT(*) AS like_count, MAX(CASE WHEN user_id = 123 THEN 1 ELSE 0 END) AS has_liked FROM likes WHERE target_type = 'post' AND target_id = 456;
这个查询能命中 uk_user_target 索引的前缀(target_type, target_id),性能可控。注意:如果 likes 表数据量超千万,建议把计数结果缓存到 Redis,只在点赞/取消时更新缓存和 DB。
收藏表要不要记录来源渠道或设备信息
一般不需要。收藏本质是用户对内容的长期兴趣标记,和操作路径无关。加 source 或 device_id 字段只会增大索引体积、拖慢写入,且几乎没查询场景用到。例外情况只有做反作弊——比如限制同一设备 1 小时内最多收藏 10 篇,这时才需要,但应单独建轻量级风控日志表,而不是污染主收藏表。
target_type 字段加枚举校验或应用层白名单控制。一旦写入非法值如 'post_xxx' 或拼写错误的 'posst',后续所有按类型聚合的统计都会漏数据,而且很难回溯修复。










