如何通过Prisma约束确保数据库始终存在至少一条parent为true的Category记录?
实现数据库常驻至少一个顶级分类的落地方案
这个需求本质是要守住一个全局状态底线:不管执行什么写操作,Category表里parent为true的记录数量永远不能降到0。只在单个mutation里零散加校验很容易遗漏其他写场景,还容易踩并发竞态的坑。
可落地的实现路径
- 应用层事务校验
别在更新操作前面裸加查询校验——把校验和写操作拆成两步独立执行的话,很容易被并发请求打穿:比如两个请求同时查到“除当前要改的记录外还剩1个顶级分类”,同时把自己操作的顶级分类改成子分类,最后就会搞到表里一个顶级分类都不剩。
正确的做法是用数据库事务把校验和更新包成原子操作,针对你现在的updateCategory逻辑,可以直接改成这样:
这套校验逻辑需要同步覆盖所有可能影响顶级分类数量的操作:删除分类、新建分类、批量更新分类的流程,都要检查操作会不会让顶级分类数量降到0,别只改这一个接口就完事。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, }, }) }) } - 数据库层兜底约束
靠人记着在所有接口里加校验是不靠谱的,后面迭代加新接口、批量改数据的时候很容易漏。Prisma本身的schema约束写不了这种“表中至少存在某类记录”的规则,所以可以手动在数据库里加个触发器:不管你对Category表做什么插入、更新、删除操作,操作完自动数下parent=true的记录数,要是数出来是0,直接回滚抛错。有了这层兜底,哪怕应用层代码漏了校验,数据库也不会让非法状态落库。
需不需要专门在mutation里手动加校验
不是硬性要求必须在每个mutation里手写一遍校验,但所有会改parent值、删分类、加分类的入口,都必须把这个底线兜住。如果觉得重复写太麻烦,可以把校验逻辑抽成公共函数复用,没必要每个接口里写一遍一样的count逻辑。
内容的提问来源于stack exchange,提问作者Dileet
相关产品推荐
相关产品推荐

