sql.md

·interview-questions

常用命令

手撕

多级评论的表,你会设计哪几个关键字段?

  • 标识和关联类字段
    • id
      • 评论主键 ID
      • BIGINT,AUTO_INCREMENT
      • 唯一标识一条评论
    • target_type
      • 被评论的对象类型,如文章、视频、动态、消息等
      • TYINYINT 或 ENUM
    • target_id
      • 被评论对象的 ID
      • BIGINT
  • 支撑多级的关键结构字段:父子关系 + 根节点 + 层级
    • parent_id
      • 父评论 ID,顶级评论为 0 或 NULL
      • BIGINT
      • 表示当前评论的直接上级是谁,即我直接回复的是谁
      • 可以用来快速查询某条评论下的直接子评论:where parent_id = ?
      • 前端也可以用来展示被回复的对象
    • root_id
      • 根评论 ID,是整棵树的入口
      • BIGINT
      • 对于顶级评论: root_id = id 自己
      • 对于任意子评论:root_id = 顶级评论的 id
      • 可以一次性查出某个主楼下所有的楼中楼:where root_id = :top_comment_id order by created_at asc
    • level
      • 评论的层级,顶级评论为 1,一级回复为 2,二级回复为 3,以此类推
      • TINYINT
      • 可以用于限制评论的最大层级,避免无限递归
  • 用户和内容相关字段
    • user_id
      • 评论用户 ID
      • BIGINT
      • 指向用户表的外键
    • reply_to_user_id
      • 被回复的用户 ID
      • BIGINT
      • 当 A 回复 B 的评论时,reply_to_user_id = B 的 user_id
      • 可以用来在评论中显示 回复 @用户名
    • content
      • 评论内容
      • TEXT 或 VARCHAR(500)
      • 如果有富文本/图片,可以再配一个 extra JSON 字段存储额外信息
    • created_at / updated_at
      • 评论创建和更新时间
      • DATETIME 或 TIMESTAMP
      • 用于排序和展示评论时间
  • 状态和统计字段
    • status
      • 评论状态,如正常:0、删除:1、审核中:2等
      • TINYINT
    • is_pinned
      • 是否置顶评论
      • BOOLEAN
    • is_deleted
      • 软删除标志
      • TINYINT(1)
      • 真删除会破坏楼中楼结构,一般先逻辑删,再看情况做物理清理
    • like_count
      • 点赞数
      • INT
      • 真正的点赞明细可以放在单独的评论点赞表,这里做缓存计数
      • 可以定期异步更新,避免频繁写评论表
    • reply_count
      • 回复数
      • INT
      • 统计某条评论下的直接子评论数

target_type + target_id 是评论所属对象,让我们知道这条评论是挂在哪个资源上的,还可以做索引index idx_target (target_type, target_id, root_id, created_at),用来查比如某篇文章下的所有评论树

like_count 和 reply_count 是常用的冗余统计字段,在点赞或回复成功后可以先写一条明细记录,同时对应的评论记录做增量更新,避免每次都去 count 点赞表或评论表,提升查询性能

完整 SQL:

CREATE TABLE `comments` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '评论主键 ID',
  
  `target_type` TINYINT NOT NULL COMMENT '被评论对象类型:1=文章,2=视频,3=动态,4=消息等',
  `target_id` BIGINT UNSIGNED NOT NULL COMMENT '被评论对象 ID',

  `parent_id` BIGINT UNSIGNED NULL DEFAULT NULL COMMENT '父评论 ID,顶级评论为 NULL',
  `root_id`  BIGINT UNSIGNED NULL DEFAULT NULL COMMENT '根评论 ID,顶级评论为自身 ID',
  `level`    TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '评论层级:1=顶级,2=回复,3=子回复……',

  `user_id`          BIGINT UNSIGNED NOT NULL COMMENT '评论用户 ID,指向用户表',
  `reply_to_user_id` BIGINT UNSIGNED NULL DEFAULT NULL COMMENT '被回复的用户 ID',

  `content` TEXT NOT NULL COMMENT '评论内容',
  `extra`   JSON NULL COMMENT '扩展信息(图片、表情、@信息等,可选)',

  `status`     TINYINT NOT NULL DEFAULT 0 COMMENT '评论状态:0=正常,1=删除/屏蔽,2=审核中等',
  `is_pinned`  TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否置顶评论:0=否,1=是',
  `is_deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否软删除:0=否,1=是',

  `like_count`  INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '点赞数(冗余字段)',
  `reply_count` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '回复数(冗余字段)',

  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP 
               ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',

  PRIMARY KEY (`id`),

  -- 拉某个目标下的评论树:按时间排序分页
  KEY `idx_target_root_time` (`target_type`, `target_id`, `root_id`, `created_at`),
  -- 查某条评论下的直接子评论
  KEY `idx_parent_time` (`parent_id`, `created_at`),
  -- 查一整个主楼的所有楼中楼(如果不按 target 维度也要这么查)
  KEY `idx_root_time` (`root_id`, `created_at`),
  -- 查某个用户的历史评论
  KEY `idx_user_time` (`user_id`, `created_at`),
  -- 查“谁回复了我”的列表
  KEY `idx_reply_to_user_time` (`reply_to_user_id`, `created_at`),
  -- 专门针对顶级评论查询
  KEY `idx_target_top_level` (`target_type`, `target_id`, `level`, `created_at`) WHERE `parent_id` IS NULL

  -- 外键约束(如果你线上不想要外键,可以删掉这些约束,只保留字段和索引)
  CONSTRAINT `fk_comments_user` 
    FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_comments_reply_user` 
    FOREIGN KEY (`reply_to_user_id`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_comments_parent` 
    FOREIGN KEY (`parent_id`) REFERENCES `comments` (`id`),
  CONSTRAINT `fk_comments_root` 
    FOREIGN KEY (`root_id`) REFERENCES `comments` (`id`)
) ENGINE=InnoDB 
  DEFAULT CHARSET = utf8mb4 
  COLLATE = utf8mb4_unicode_ci
  COMMENT='多级评论表';

写 sql,查询二级评论最多的一级评论,取前十条

A:

-- 查询前 10 个二级评论最多的一级评论 ID 及其回复数
SELECT
root_id AS comment_id,
COUNT(*) AS reply_count
FROM comment
WHERE level = 2
GROUP BY root_id
ORDER BY reply_count DESC
LIMIT 10;

思路:先把所有二级评论(level=2)按 root_id 聚合,计算每个 root 的回复数,再按数量倒序取前 10