回复评论的层级:
树形结构,资源 -> 一级评论 -> 二级评论 -> 三级评论
树形结构设计的几种思路
- 邻接表:有一个parent_id的列,指向父节点,值一般都是节点的主键。根节点的parent_id可以用NULL表示,也可以用非法值(比如-1表示)
- 分段式path:有一列维持根节点到当前节点的路径。比如path的值式a/b/c,那么根据路径的定义分别由两种解释,如果路径包含当前节点,当前节点就是c,父节点是b,根节点是a;如果路径不包含当前节点,c是当前节点的父节点。
- Nested Set:需要维持住两个额外的列nsleft和nsright,其中nsleft小于所有子节点的nsleft,nsright大于所有子节点的nsright,类似于二叉搜索树的概念,两个列的取值,也相当于是dfs的顺序
- Closure Table:用一张额外的表维护各个节点之间的关系,比如ancestor代表的是祖先节点,descendant表示后继节点。一个节点和后继节点会有一行数据,一个节点和祖先节点也会有一行数据,所以会产生大量的数据
评论的业务功能
- 加载某资源的第一页评论
- 加载某资源的第一页评论的直接评论,分批加载
- 查看某评论的更多评论
其中功能1是根据业务类型和业务id来查询,功能2-3都是查询某个节点的子节点。所以优先考虑思路1邻接表和思路2分段式path。思路2分段式path里,查询子节点是使用Like来查询的,性能较差。先考虑思路1邻接表的实现
根据思路1邻接表的写法,结合具体的业务,可以得到表结构设计如下:
- Uid:表示发表评论的用户的唯一标识
- Biz:表示该评论的业务场景,例如视频、评论、用户
- BizID:表示评论的资源的唯一表示
- RootID:根ID
- PID:父评论的ID
- ParentComment:外键约束,为了级联删除。指定外键使用的是PID,关联外键的字段是ID,OnDelete:CASCADE设置删除策略采用级联删除
type Comment struct {
Id int64 `gorm:"autoIncrement,primaryKey"`
Uid int64
Biz string `gorm:"index:biz_type_id"`
BizID int64 `gorm:"index:biz_type_id"`
Content string
RootID sql.NullInt64 `gorm:"column:root_id;index"`
PID sql.NullInt64 `gorm:"column:pid;index"`
ParentComment *Comment `gorm:"ForeignKey:PID;AssociationForeignKey:ID;constraint:OnDelete:CASCADE"`
Ctime int64
Utime int64
}
索引设计如下:
- Uid:可选,比如如果需要查询某个用户发表过的所有评论,就需要该索引
- Biz+BizID:解决查找某个资源的评论的场景
- PID:为了查找子节点,某个评论下面的所有一级回复
- Ctime/Utime:看是否需要排序