跳到主要内容

事务与并发控制

问题

什么是数据库事务?四种隔离级别分别解决什么问题?MVCC 是什么?

答案

ACID 特性

特性说明实现
Atomicity 原子性事务中操作全部成功或全部回滚undo log
Consistency 一致性事务前后数据状态一致其他三者保证
Isolation 隔离性并发事务互不干扰锁 + MVCC
Durability 持久性事务提交后数据永久保存redo log

并发问题

问题说明示例
脏读读到未提交的数据A 未提交的修改被 B 读到
不可重复读同事务两次读不一致A 两次读之间 B 修改了数据
幻读同条件两次查结果行数不同A 两次查之间 B 插入了新行

四种隔离级别

级别脏读不可重复读幻读性能
Read Uncommitted最高
Read Committed(RC)
Repeatable Read(RR)❌*
Serializable最低
MySQL 默认

MySQL InnoDB 默认使用 Repeatable Read,但通过 MVCC + 间隙锁解决了大部分幻读问题。PostgreSQL 默认使用 Read Committed

MVCC 多版本并发控制

MVCC 核心:每行数据保存多个版本,通过 Read View(快照)决定事务能看到哪个版本,实现读不加锁,读写不冲突

Node.js 中使用事务

transaction.ts
// Prisma 事务
async function transfer(fromId: string, toId: string, amount: number) {
await prisma.$transaction(async (tx) => {
const from = await tx.account.update({
where: { id: fromId },
data: { balance: { decrement: amount } },
});

if (from.balance < 0) {
throw new Error('余额不足'); // 自动回滚
}

await tx.account.update({
where: { id: toId },
data: { balance: { increment: amount } },
});
});
}

// TypeORM 事务
await dataSource.transaction(async (manager) => {
await manager.save(order);
await manager.save(orderItems);
await manager.update(Product, productId, { stock: () => 'stock - 1' });
});

常见面试问题

Q1: 乐观锁和悲观锁的区别?

答案

维度悲观锁乐观锁
思路先加锁再操作先操作再检查冲突
实现SELECT ... FOR UPDATE版本号 / CAS
适用写多读少、冲突频繁读多写少、冲突较少
-- 悲观锁
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;

-- 乐观锁(版本号)
UPDATE accounts SET balance = 200, version = version + 1
WHERE id = 1 AND version = 5;

Q2: 数据库死锁怎么解决?

答案

  1. 多个事务按相同顺序访问资源
  2. 设置锁等待超时
  3. InnoDB 会自动检测并回滚一个事务
  4. 减小事务范围,降低锁冲突

Q3: RR 级别下如何解决幻读?

答案

InnoDB 用间隙锁(Gap Lock) 锁住索引之间的间隙,阻止其他事务在间隙中插入新行。

相关链接