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

MySQL软删除与唯一约束共存的最佳实践及Prisma相关解决方案咨询

MySQL软删除与唯一约束共存的最佳实践及Prisma相关解决方案咨询

嘿,这个问题我在项目里碰过好多次了——软删除要保留数据历史,可唯一约束又卡着不让重复值复用,确实头疼!下面分享下MySQL端的最佳实践,还有Prisma里怎么适配:

MySQL 端的核心解决方案

1. 复合唯一索引(首选方案)

这是最通用也最可靠的做法,把原来单一的email唯一索引,改成email+deleted_at的复合唯一索引。

原理很简单:MySQL里NULL值不视为相等,所以**未删除的记录(deleted_at为NULL)**之间,email不能重复;而软删除的记录(deleted_at有时间戳),因为每个记录的deleted_at值不同,哪怕email相同也不会触发唯一约束;新插入的未删除记录(deleted_at为NULL)和软删除的同email记录也不会冲突,完美契合需求。

修改后的表结构如下:

CREATE TABLE users (
  id INTEGER NOT NULL AUTO_INCREMENT,
  email VARCHAR(50) NOT NULL,
  deleted_at TIMESTAMP(0) NULL,
  UNIQUE INDEX `users_id_unique` (`id`),
  -- 替换原单一email唯一索引
  UNIQUE INDEX `users_email_deleted_at_unique` (`email`, `deleted_at`),
  PRIMARY KEY (`id`)
);

2. 归档表分离(适合严格归档场景)

如果你的业务对主表数据纯净度要求很高,也可以在软删除时把记录物理迁移到归档表,主表只保留活跃数据。这样主表的单一email唯一索引就能正常工作,不会有冲突问题。

但这个方案的缺点是失去了软删除“原表留存、快速恢复”的优势,适合不需要频繁恢复删除数据的场景。

3. 业务层/触发器校验(不推荐)

也可以保留原单一唯一索引,在插入/更新时通过业务逻辑或者触发器,检查是否存在未删除的同email记录。但这种方案可靠性不如数据库层面的约束——业务层容易出现逻辑漏洞,触发器也可能因为事务、并发场景失效,所以只作为备选。

Prisma 中的适配方案

Prisma本身没有内置的软删除+唯一约束的专属支持,但可以通过以下方式完美适配上面的MySQL方案:

1. 模型定义复合唯一约束

在schema.prisma里直接定义email和deletedAt的复合唯一约束,执行迁移后会自动生成对应的MySQL复合索引:

model User {
  id        Int      @id @default(autoincrement())
  email     String
  deletedAt DateTime?
  -- 声明复合唯一约束
  @@unique([email, deletedAt])
}

2. 封装软删除与创建逻辑

可以在业务层封装通用的软删除和用户创建方法,既保证逻辑统一,又能返回更友好的错误提示:

import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

// 创建用户前先检查未删除的同邮箱记录
async function createUser(data: { email: string }) {
  const existingActiveUser = await prisma.user.findFirst({
    where: {
      email: data.email,
      deletedAt: null
    }
  });

  if (existingActiveUser) {
    throw new Error("该邮箱已被活跃用户使用");
  }

  return await prisma.user.create({ data });
}

// 封装软删除方法
async function softDeleteUser(userId: number) {
  return await prisma.user.update({
    where: { id: userId },
    data: { deletedAt: new Date() }
  });
}

3. 全局中间件自动过滤软删除记录

用Prisma Middleware可以全局处理软删除记录的过滤,避免每次查询都手动添加deletedAt: null的条件:

prisma.$use(async (params, next) => {
  // 针对User模型的查询操作自动过滤已删除记录
  if (params.model === 'User') {
    switch (params.action) {
      case 'findUnique':
      case 'findFirst':
        params.args.where = { ...params.args.where, deletedAt: null };
        break;
      case 'findMany':
        params.args.where = { ...(params.args.where || {}), deletedAt: null };
        break;
    }
  }
  return next(params);
});

这样在调用prisma.user.findFirst({ where: { email: 'xxx@xxx.com' } })时,会自动只查询未删除的用户,无需额外条件。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 03:08:46