如何在数据库中实现双向好友关系及数据完整性与原子性保障?
如何实现双向好友关系并保证数据完整性与原子性?
双向好友关系和单向关注的核心区别是对称性——A是B的好友意味着B必然是A的好友,不需要存储两条重复记录,反而要避免这种冗余。下面是具体的实现方案,兼顾数据完整性、原子性和Prisma的使用:
1. 核心设计原则
- 只存储唯一的好友对记录:通过约束确保好友对(A,B)只会以
min(A,B), max(A,B)的形式存储一次,杜绝重复。 - 用数据库级约束保证数据合法性:避免脏数据(比如自己加自己为好友、重复添加好友)。
- 事务确保操作原子性:所有好友关系的变更(添加/删除)都在单个事务中执行,要么全成要么全滚。
2. 数据库表结构(SQL)
CREATE TABLE "User" ( id integer DEFAULT nextval('"User_id_seq"'::regclass) PRIMARY KEY, name text ); CREATE TABLE "Friendship" ( "user1Id" integer NOT NULL REFERENCES "User"(id) ON DELETE CASCADE ON UPDATE CASCADE, "user2Id" integer NOT NULL REFERENCES "User"(id) ON DELETE CASCADE ON UPDATE CASCADE, "createdAt" timestamp with time zone DEFAULT now(), PRIMARY KEY ("user1Id", "user2Id"), CHECK ("user1Id" < "user2Id") -- 核心约束:确保好友对唯一存储 );
CHECK ("user1Id" < "user2Id"):强制存储时小ID在前,大ID在后,避免(A,B)和(B,A)两条重复记录。- 复合主键
("user1Id", "user2Id"):确保同一个好友对不会被多次插入。 ON DELETE CASCADE:用户被删除时,自动清理其所有好友关系,避免孤儿数据。
3. Prisma模型定义
model User { id Int @id @default(autoincrement()) name String? // 关联两种方向的好友关系 friends1 Friendship[] @relation("User1Friend") friends2 Friendship[] @relation("User2Friend") } model Friendship { user1 User @relation("User1Friend", fields: [user1Id], references: [id]) user1Id Int user2 User @relation("User2Friend", fields: [user2Id], references: [id]) user2Id Int createdAt DateTime @default(now()) @@id([user1Id, user2Id]) @@db.Check("user1Id < user2Id") // 同步数据库的CHECK约束 }
4. 保证数据完整性与原子性的关键操作
添加好友
先对两个用户ID排序,确保符合user1Id < user2Id的约束,然后在单个事务中插入记录:
async function addFriend(userId1: number, userId2: number) { // 禁止自己加自己为好友 if (userId1 === userId2) throw new Error("Cannot add yourself as a friend"); // 排序确保符合约束 const [user1Id, user2Id] = userId1 < userId2 ? [userId1, userId2] : [userId2, userId1]; // Prisma的create操作默认在事务中执行,确保原子性 return prisma.friendship.create({ data: { user1Id, user2Id }, }); }
删除好友
同样先排序找到对应的唯一记录,删除后双向关系自然同步消失:
async function removeFriend(userId1: number, userId2: number) { const [user1Id, user2Id] = userId1 < userId2 ? [userId1, userId2] : [userId2, userId1]; return prisma.friendship.delete({ where: { user1Id_user2Id: { user1Id, user2Id } }, }); }
查询好友列表
需要合并两种方向的关联记录,得到完整的好友列表:
async function getUserFriends(userId: number) { const user = await prisma.user.findUnique({ where: { id: userId }, include: { friends1: { include: { user2: true } }, friends2: { include: { user1: true } }, }, }); if (!user) return []; // 合并两个关系中的好友对象 return [ ...user.friends1.map(friendship => friendship.user2), ...user.friends2.map(friendship => friendship.user1), ]; }
为什么这比存两条记录更好?
- 避免数据冗余:不需要同时存(A,B)和(B,A),减少存储空间和潜在的不一致风险。
- 简化操作:删除时只需处理一条记录,不会出现漏删某一侧的情况。
- 数据库约束兜底:即使应用层逻辑出错,数据库的CHECK和主键约束也会阻止脏数据插入。
内容的提问来源于stack exchange,提问作者Artyom Ionash
相关产品推荐
相关产品推荐

