You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Prisma约束确保数据库始终存在至少一条parent为true的Category记录?

实现数据库常驻至少一个顶级分类的落地方案

这个需求本质是要守住一个全局状态底线:不管执行什么写操作,Category表里parent为true的记录数量永远不能降到0。只在单个mutation里零散加校验很容易遗漏其他写场景,还容易踩并发竞态的坑。

可落地的实现路径

  • 应用层事务校验
    别在更新操作前面裸加查询校验——把校验和写操作拆成两步独立执行的话,很容易被并发请求打穿:比如两个请求同时查到“除当前要改的记录外还剩1个顶级分类”,同时把自己操作的顶级分类改成子分类,最后就会搞到表里一个顶级分类都不剩。
    正确的做法是用数据库事务把校验和更新包成原子操作,针对你现在的updateCategory逻辑,可以直接改成这样:
    export default async function updateCategory({ where, data }: UpdateCategoryInput, ctx: Ctx) {
      const { title, parent, parentId } = CategoryInput.parse(data)
    
      // 使用Prisma交互式事务包裹校验和更新流程
      return ctx.db.$transaction(async (tx) => {
        // 先查询待更新分类的当前状态,避免对不需要校验的操作做多余查询
        const currentCategory = await tx.category.findUniqueOrThrow({
          where,
          select: { parent: true }
        })
    
        // 仅当操作会把原本的顶级分类改成普通子分类时,才需要触发校验
        const isConvertingParentToChild = currentCategory.parent === true && parent === false
        if (isConvertingParentToChild) {
          // 统计排除当前记录后,剩余的顶级分类数量
          const remainingParentCount = await tx.category.count({
            where: {
              parent: true,
              NOT: where
            }
          })
          // 没有剩余顶级分类时直接抛错,阻止非法更新
          if (remainingParentCount === 0) {
            throw new Error('系统必须始终保留至少一个顶级分类,无法将最后一个顶级分类修改为子分类')
          }
        }
    
        // 校验通过后执行实际更新
        return tx.category.update({
          where,
          data: {
            title,
            parent,
            parentId: parent ? null : parentId,
          },
        })
      })
    }
    
    这套校验逻辑需要同步覆盖所有可能影响顶级分类数量的操作:删除分类、新建分类、批量更新分类的流程,都要检查操作会不会让顶级分类数量降到0,别只改这一个接口就完事。
  • 数据库层兜底约束
    靠人记着在所有接口里加校验是不靠谱的,后面迭代加新接口、批量改数据的时候很容易漏。Prisma本身的schema约束写不了这种“表中至少存在某类记录”的规则,所以可以手动在数据库里加个触发器:不管你对Category表做什么插入、更新、删除操作,操作完自动数下parent=true的记录数,要是数出来是0,直接回滚抛错。有了这层兜底,哪怕应用层代码漏了校验,数据库也不会让非法状态落库。

需不需要专门在mutation里手动加校验

不是硬性要求必须在每个mutation里手写一遍校验,但所有会改parent值、删分类、加分类的入口,都必须把这个底线兜住。如果觉得重复写太麻烦,可以把校验逻辑抽成公共函数复用,没必要每个接口里写一遍一样的count逻辑。


内容的提问来源于stack exchange,提问作者Dileet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.02 10:27:08