# MVCC 与死锁

URL: https://caijiao.org/postgresql/performance/transactions/mvcc
Source: docs/postgresql/performance/transactions/mvcc.md
Description: 本章节介绍 PostgreSQL 中的多版本并发控制（MVCC）机制及其与死锁的关系，帮助理解并发事务的控制和死锁的处理方式。

PostgreSQL 使用多版本并发控制（MVCC）来处理并发事务的执行，保证数据的一致性和高效的并发性。MVCC 使得多个事务可以同时进行，而不会相互干扰。虽然 MVCC 能大大提升并发性能，但在某些情况下，也可能导致死锁问题。

## 什么是 MVCC（多版本并发控制）

MVCC 是一种通过存储多个数据版本来实现并发控制的机制。在 PostgreSQL 中，MVCC 通过为每个数据行维护多个版本，使得每个事务可以看到它开始时的快照数据，而不会受到其他事务的影响。这意味着读取操作不会阻塞写操作，写操作也不会阻塞读取操作，从而提高了并发性能。

### MVCC 的工作原理

每当一个事务修改数据时，PostgreSQL 不会直接修改原始数据行，而是创建该行的一个新版本。每个数据行都有两个隐藏的系统列：`xmin` 和 `xmax`，分别表示该行的创建事务和删除事务的 ID。通过这些标识，PostgreSQL 可以区分行的不同版本，从而保证事务的一致性和隔离性。

- **xmin**：数据行的创建事务 ID。
- **xmax**：数据行的删除事务 ID。

### MVCC 的优势

- **提高并发性**：读取操作不需要等待写操作完成，可以并发执行。
- **避免阻塞**：事务可以在不互相干扰的情况下进行，从而减少锁的竞争。

### MVCC 的缺点

- **空间消耗**：随着数据的修改，旧版本的行会保留在数据库中，可能导致磁盘空间的浪费。
- **VACUUM 操作**：为了清理无用的旧版本数据，需要定期运行 `VACUUM` 操作。

## 什么是死锁

死锁是指两个或多个事务在执行过程中，由于相互等待对方持有的锁而造成的僵局，无法继续执行。死锁通常发生在两个事务试图获取对方已经持有的锁时，导致事务无法继续进行。

### 死锁的发生条件

死锁的发生需要满足以下四个条件：

1. **互斥条件**：每个资源只能由一个事务占用。
2. **持有并等待**：事务持有一些资源，并等待获取其他资源。
3. **非抢占条件**：资源不能被强制剥夺，只能由事务释放。
4. **循环等待**：事务间形成一个环形等待链，彼此互相等待资源。

### 死锁的检测与处理

PostgreSQL 使用基于图的死锁检测算法来识别死锁。当系统检测到死锁时，会选择一个事务进行回滚，释放其占用的锁，从而打破死锁循环，恢复正常操作。

### 死锁的解决方式

- **自动回滚**：PostgreSQL 会自动检测到死锁并回滚其中一个事务，释放锁资源，从而避免系统完全停滞。
- **应用级别的控制**：开发者可以通过减少事务的持续时间、避免复杂的锁顺序等方式来减少死锁的发生概率。

## MVCC 与死锁的关系

尽管 MVCC 能有效地提高事务并发性和减少锁的竞争，但它也可能在一些情况下引发死锁问题，特别是在复杂的事务操作中。例如，事务在同时修改多个数据行时，可能会产生死锁。如果两个事务在操作时互相依赖对方已经持有的行锁，并且没有适当的锁顺序控制，就可能导致死锁。

### 如何避免死锁

1. **降低事务的粒度**：减少每个事务操作的数据量和操作时间，避免长时间占用锁。
2. **统一锁顺序**：在多个事务中，确保对资源的访问顺序是一致的，从而避免循环等待。
3. **合理使用锁**：在适当的情况下，可以使用显式锁（如 `FOR UPDATE`）来控制事务的并发性。
4. **使用 `NOWAIT` 或 `SKIP LOCKED`**：这些选项可以避免在锁被占用时等待，减少死锁的发生。

## 小结

MVCC 是 PostgreSQL 的一个重要特性，它通过支持并发读取和写入，提高了数据库的性能和并发处理能力。然而，在并发事务操作中，特别是在涉及多个资源的事务中，死锁依然可能发生。了解 MVCC 和死锁的工作原理，并采取相应的措施优化事务设计，可以有效避免性能瓶颈和事务冲突，确保数据库的高效运行。
