点赞和收藏功能在 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 前做简单校验。
