点赞和收藏需用独立的 user_likes 和 user_favorites 表建模多对多关系,字段含 user_id、target_type、target_id,联合唯一索引防重复;内容表冗余 like_count/favorite_count 并原子更新;取消操作需 where 全条件删除并检查影响行数。

点赞和收藏功能在 MySQL 中不是靠单表搞定的,核心在于「用户行为」与「内容实体」之间的多对多关系建模,且必须区分 like 和 favorite 两种独立行为。
用什么表结构存点赞和收藏
不能把 is_liked、is_favored 直接加到文章/视频/帖子表里——这会导致更新锁竞争、无法查“谁点过赞”、不支持取消再点、也不利于统计和分页拉取用户行为列表。
- 建一张
user_likes表:user_id(主键之一)、target_type(如'post'、'comment')、target_id(被点赞的记录 ID)、created_at;联合唯一索引为(user_id, target_type, target_id) - 同理建
user_favorites表,字段结构一致;复用同一套逻辑但物理分离,方便后期各自加字段(比如收藏可加folder_id,点赞不需要) -
target_type字段用字符串而非外键,避免跨表约束和迁移成本;查询时用WHERE target_type = 'post' AND target_id = 123即可
怎么防止重复点赞或重复收藏
靠数据库层唯一约束最可靠,应用层判断只是优化体验,不能替代它。
- 插入前不做
SELECT判断(存在竞态:A 查无记录 → B 插入 → A 再插入,重复) - 直接执行
INSERT INTO user_likes (user_id, target_type, target_id) VALUES (101, 'post', 2001),配合ON DUPLICATE KEY UPDATE created_at = NOW()(如果已存在就刷新时间,适合“重新点赞”语义) - 或者用
INSERT IGNORE忽略重复错误(适合纯开关型操作);MySQL 返回影响行数:1 表示新增,0 表示已存在 - 注意:唯一索引必须包含
user_id + target_type + target_id全部三列,漏掉任一列都会失效
怎么高效查某条内容被赞了多少次
不要每次查都 COUNT(*) FROM user_likes WHERE target_type='post' AND target_id=123 —— 高频读场景下会拖慢主库,尤其当行为表上亿行时。
- 在内容表(如
posts)里加冗余字段:like_count和favorite_count,类型用INT UNSIGNED - 点赞/取消点赞时,用原子操作更新:
UPDATE posts SET like_count = like_count + 1 WHERE id = 123(+1 或 -1) - 该字段只用于展示总数;真实行为明细仍以
user_likes表为准(比如做防刷、审计、通知等) - 如果业务要求强一致性(如秒杀式点赞排行榜),可用 Redis 的
INCR/DECR做前置计数,再异步落库,但多数社交场景用数据库原子更新足够
取消点赞或取消收藏怎么写才安全
删除操作比插入更容易出错,关键是「删对了人、删对了目标、且知道删没删成」。
- 用
DELETE FROM user_likes WHERE user_id = 101 AND target_type = 'post' AND target_id = 123,不要只用target_id - 检查
ROW_COUNT()返回值:0 表示本来就没点过,1 表示成功取消;这是判断“当前状态”的唯一依据 - 不要先
SELECT再DELETE—— 同样有竞态;也不要依赖返回的“影响行数是否为 0”来报错,而应视作正常流程分支 - 如果要同时更新计数,建议用事务包裹:
START TRANSACTION; DELETE FROM user_likes WHERE user_id = 101 AND target_type = 'post' AND target_id = 123; UPDATE posts SET like_count = like_count - 1 WHERE id = 123; COMMIT;
,并确保posts.id有主键索引,否则UPDATE可能锁表
最容易被忽略的是 target_type 的枚举管理——它散落在 SQL、代码、文档里,没人维护就会出现 'Post'、'post'、'POST' 混用,导致查询永远为空;建议在应用层定义常量,在建表注释里写明合法值,并在 INSERT 前做简单校验。










