乐观锁是一种思想,不实际加锁,通过版本号控制一致性;悲观锁是在数据库中加行锁或表锁。Prisma 的文档中有实现乐观锁的示例,也可以通过执行原生 SQL 语句实现悲观锁。
乐观锁
Prisma 实现乐观锁,可在表中添加一个 version
字段,并在更新操作中使用该字段来确保数据的一致性。
比如在官网的文档中,给了一个具体的示例,展示了如何在电影订票系统中使用乐观锁,来避免双重预订问题。
在模型中添加一个 version
字段:
model Seat {
id Int @id @default(autoincrement())
userId Int?
claimedBy User? @relation(fields: [userId], references: [id])
movieId Int
movie Movie @relation(fields: [movieId], references: [id])
version Int
}
model Movie {
id Int @id @default(autoincrement())
name String @unique
seats Seat[]
}
代码示例:
const movieName = 'Hidden Figures'
// 查找第一个可用座位
const availableSeat = await prisma.seat.findFirst({
where: {
movie: {
name: movieName,
},
claimedBy: null,
},
})
// 如果没有座位,则抛出一个错误
if (!availableSeat) {
throw new Error(`Oh no! ${movieName} is all booked.`)
}
// 通过乐观锁定来获得座位,所有其他尝试预定同一个座位会有一个过时的版本
const seats = await prisma.seat.updateMany({
data: {
claimedBy: userId,
version: {
increment: 1,
},
},
where: {
id: availableSeat.id,
version: availableSeat.version,
},
})
if (seats.count === 0) {
throw new Error(`座位已被预订!请再试一次。`)
}
以上示例中,updateMany
方法会检查 version
字段是否匹配。如果在此期间有其他事务修改了该记录,version
字段将不匹配,更新操作将失败,从而避免数据不一致的问题。
通过这种方式,Prisma 可以有效地实现乐观锁,确保数据的一致性和安全性。
悲观锁
Prisma 目前不直接支持悲观锁(例如 SELECT FOR UPDATE
或 SELECT FOR SHARE
),可使用 $executeRaw
方法来执行原生 SQL 查询,从而实现悲观锁。
https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries
操作流程:
- 开始事务:使用
prisma.$transaction
方法开始一个事务。 - 锁定记录:使用
SELECT * FROM "User" WHERE id = 1 FOR UPDATE
锁定指定的记录。这将防止其他事务在当前事务完成之前修改该记录。 - 更新记录:在锁定的记录上执行更新操作。
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
async function main() {
// 开始事务
await prisma.$transaction(async (tx) => {
// 使用 FOR UPDATE 锁定记录
const user = await tx.$queryRaw`SELECT * FROM "User" WHERE id = 1 FOR UPDATE`;
// 在锁定的记录上执行更新操作
await tx.user.update({
where: { id: 1 },
data: { status: 'INACTIVE' },
});
});
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});