4.4.9 数据如何保证一致——事务处理:数据一致性保证
一句话破题
事务让多个操作要么全部成功、要么全部回滚——这是保证数据一致性的核心机制。
Prisma 事务类型
| 类型 | 用途 | 特点 |
|---|---|---|
| 顺序事务 | 多个独立操作 | 简单,无法在操作间加逻辑 |
| 交互式事务 | 有依赖的操作 | 灵活,可以在操作间加业务逻辑 |
顺序事务
typescript
// 多个操作作为数组传入,按顺序执行
const [user, post] = await prisma.$transaction([
prisma.user.create({
data: { email: 'user@example.com', name: '用户' }
}),
prisma.post.create({
data: { title: '文章', authorId: 'xxx' }
})
])适用场景:多个独立操作需要原子性执行
交互式事务
typescript
// 可以在事务中编写业务逻辑
const result = await prisma.$transaction(async (tx) => {
// 1. 查询当前余额
const account = await tx.account.findUnique({
where: { userId: senderId }
})
// 2. 业务逻辑判断
if (!account || account.balance < amount) {
throw new Error('余额不足')
}
// 3. 扣减发送方余额
await tx.account.update({
where: { userId: senderId },
data: { balance: { decrement: amount } }
})
// 4. 增加接收方余额
await tx.account.update({
where: { userId: receiverId },
data: { balance: { increment: amount } }
})
// 5. 记录交易日志
const transaction = await tx.transaction.create({
data: {
senderId,
receiverId,
amount,
type: 'TRANSFER'
}
})
return transaction
})事务配置选项
typescript
await prisma.$transaction(
async (tx) => {
// ...
},
{
maxWait: 5000, // 等待获取事务的最长时间(毫秒)
timeout: 10000, // 事务执行的最长时间(毫秒)
isolationLevel: 'Serializable' // 隔离级别
}
)隔离级别:
| 级别 | 说明 |
|---|---|
ReadUncommitted | 最低隔离,可能读到未提交的数据 |
ReadCommitted | 只读已提交的数据(PostgreSQL 默认) |
RepeatableRead | 同一事务内多次读取结果一致 |
Serializable | 最高隔离,完全串行化 |
常见事务场景
场景一:创建订单 + 扣减库存
typescript
async function createOrder(userId: string, items: OrderItem[]) {
return prisma.$transaction(async (tx) => {
// 检查并扣减库存
for (const item of items) {
const product = await tx.product.findUnique({
where: { id: item.productId }
})
if (!product || product.stock < item.quantity) {
throw new Error(`商品 ${item.productId} 库存不足`)
}
await tx.product.update({
where: { id: item.productId },
data: { stock: { decrement: item.quantity } }
})
}
// 创建订单
const order = await tx.order.create({
data: {
userId,
items: {
create: items.map(item => ({
productId: item.productId,
quantity: item.quantity,
price: item.price
}))
}
}
})
return order
})
}场景二:用户注册 + 初始化数据
typescript
async function registerUser(data: RegisterData) {
return prisma.$transaction(async (tx) => {
// 创建用户
const user = await tx.user.create({
data: {
email: data.email,
name: data.name
}
})
// 创建用户配置
await tx.userSettings.create({
data: {
userId: user.id,
theme: 'light',
notifications: true
}
})
// 创建初始钱包
await tx.wallet.create({
data: {
userId: user.id,
balance: 0
}
})
return user
})
}嵌套写入 vs 事务
嵌套写入(Prisma 自动使用事务):
typescript
// 这已经是原子操作
const user = await prisma.user.create({
data: {
email: 'user@example.com',
profile: {
create: { bio: '...' }
},
posts: {
create: [
{ title: '文章1' },
{ title: '文章2' }
]
}
}
})何时需要显式事务:
- 需要在操作间加业务逻辑
- 需要跨多个顶级操作
- 需要配置事务选项
错误处理
typescript
try {
await prisma.$transaction(async (tx) => {
// ...
})
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
// Prisma 错误
if (error.code === 'P2002') {
// 唯一约束冲突
}
}
throw error
}避坑指南
事务要短:长事务会锁定资源,影响并发
不要在事务中调用外部服务:
typescript// 错误示例 await prisma.$transaction(async (tx) => { await tx.order.create({ ... }) await sendEmail() // 外部调用可能失败或很慢 }) // 正确:先事务,后调用 const order = await prisma.$transaction(async (tx) => { return tx.order.create({ ... }) }) await sendEmail() // 事务成功后再发邮件处理死锁:设置合理的超时时间
本节小结
- 顺序事务用于简单的多操作原子执行
- 交互式事务用于需要业务逻辑的场景
- 嵌套写入自动使用事务,简单场景不需要显式事务
- 事务要短,避免在事务中调用外部服务
