Skip to content

📚 DBMS


数据库事务实现原则

数据库事务的设计原则是 ACID,这是一个由四个英文单词首字母组成的缩写,代表了确保数据处理可靠性的四个核心特性。理解 ACID 原则对于设计和使用任何关系型数据库都至关重要。

原子性 (Atomicity)

原子性确保一个事务中的所有操作要么全部完成,要么全部不完成。如果事务中的任何一个操作失败,那么整个事务都会被回滚到开始前的状态,就好像这个事务从未发生过一样。

举例: 在银行转账中,从 A 账户扣款和向 B 账户加款是两个操作。原子性确保这两个操作要么都成功,要么都失败。如果从 A 账户扣款成功,但向 B 账户加款失败,那么扣款操作会自动撤销,确保 A 账户的余额保持不变。

一致性 (Consistency)

一致性确保事务在开始和结束时,数据库都保持一致状态。这意味着事务不会破坏数据库的完整性约束,例如主键约束、唯一性约束或外键约束。

举例: 如果一个表的 age 列被定义为 > 0,那么任何试图插入或更新 age 为负数的事务都将失败,从而维护了数据库的这个一致性规则。

隔离性 (Isolation)

隔离性确保多个并发事务互不干扰。每个事务都感觉自己是数据库中唯一在执行的操作。即使它们同时运行,一个事务的中间状态对其他事务来说是不可见的。

举例: 两个事务同时尝试修改同一条数据。隔离性确保它们不会互相覆盖对方的修改。数据库通过锁和 MVCC(多版本并发控制)等机制来保证隔离性。

持久性 (Durability)

持久性确保一旦事务提交,它所做的更改就会被永久保存在数据库中。即使系统发生故障(如断电或崩溃),这些更改也不会丢失。

举例: 当一个转账事务提交后,即使服务器立即崩溃,A 和 B 账户的余额变化也已经写入了硬盘,重启后仍然是正确的。数据库通过将数据写入日志文件或其他持久性存储来保证这一点。


事务隔离级别

事务隔离级别的对象

事务隔离级别(READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READSERIALIZABLE)主要是 针对事务中 DML(Data Manipulation Language,如 SELECT/INSERT/UPDATE/DELETE)的并发读写行为 而定义的。

重点在于控制 脏读不可重复读幻读 这些现象。

这些现象本质上都发生在 数据的读写(DML) 上。

DDL 是否受事务隔离级别限制?

DDL(Data Definition Language,如 CREATE TABLE/ALTER TABLE/DROP TABLE) 的行为和 DML 不一样:

大多数数据库(如 MySQL、PostgreSQL)中,DDL 操作通常是 隐式提交事务

  • 在执行 DDL 前,会自动提交当前事务。
  • 执行完 DDL 后,又会自动提交一次。

所以 DDL 往往不在用户可控的事务上下文里。

因此 DDL 一般不受事务隔离级别的限制,因为它不是在事务中“并发可见性”的问题,而是涉及到 元数据锁(metadata lock, MDL)模式锁(schema lock) 来保证一致性。

例如:

  • MySQL 里,ALTER TABLE 会获得元数据锁,阻塞其他读写该表的事务。
  • PostgreSQL 里,DDL 会拿 ACCESS EXCLUSIVE 锁,确保没有并发的 DML 影响表结构修改。

例外情况

  • Oracle 与 PostgreSQL 支持某些 DDL 可以在事务里回滚(称为 transactional DDL),但本质上还是通过锁和元数据一致性控制,而不是通过隔离级别。
  • MySQL(InnoDB)里几乎所有 DDL 都是非事务性的(除了 CREATE/DROP TEMPORARY TABLE 这种小范围情况)。

MVCC 的作用

MVCC,全称多版本并发控制,其核心作用可以概括为一句话:

在不加锁的情况下,让读操作可以和写操作并发执行,从而提高数据库的性能和吞吐量。

MVCC 的主要目的并不是为了替代锁,而是为了解决读写冲突。它通过为每行数据创建多个版本,让不同的事务可以根据需要访问不同的版本,避免了读操作因等待写操作而阻塞。

  1. 提高并发性 这是 MVCC 最重要的作用。在没有 MVCC 的系统中,如果一个事务正在修改数据(写操作),那么任何想要读取同一数据的事务(读操作)都必须等待,直到写事务完成并释放锁。这会导致严重的性能瓶颈。MVCC 通过提供数据的旧版本,让读事务可以立即执行,而不需要等待,从而大大提高了数据库的并发处理能力。

  2. 实现事务隔离 MVCC 是实现 READ COMMITTEDREPEATABLE READ 这两个关键隔离级别的主要技术。

    • READ COMMITTED 级别下,MVCC 确保每个 SELECT 语句都能看到最新的已提交数据版本,避免了“脏读”。
    • REPEATABLE READ 级别下,MVCC 确保整个事务中的所有 SELECT 语句都看到同一个数据快照,避免了“不可重复读”的问题。
  3. 减少死锁 由于读操作不再需要加锁,也就不会因为等待其他事务所持有的锁而陷入僵局,这在很大程度上减少了死锁的发生。死锁主要发生在多个事务都尝试获取对方已经持有的锁时,而 MVCC 减少了读锁的需求,从而降低了死锁的概率。

简而言之,MVCC 是一种乐观的并发控制策略。它假设读写冲突不太可能发生,因此允许它们并发执行,只有当真正发生写写冲突时,才需要使用锁来解决。这种机制极大地优化了现代高并发数据库的性能。


PostgreSQL 的隔离级别实现

PostgreSQL 的四个标准事务隔离级别(READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READSERIALIZABLE)都是通过 MVCC 和锁的组合来实现的,但具体细节与 MySQL 略有不同:

READ UNCOMMITTED

  • MVCC: 不使用。
  • : 写操作会加锁。
  • 行为: PostgreSQL 实际上将 READ UNCOMMITTED 提升到了 READ COMMITTED 级别。这意味着即使你将隔离级别设置为 READ UNCOMMITTED,它仍然会表现得像 READ COMMITTED 一样,从而避免脏读。这是一个为了保证数据完整性的设计选择。

READ COMMITTED

  • MVCC: 使用。每个 SELECT 语句都会看到一个最新的、已提交的数据版本。事务中的每个语句都会创建一个新的快照。
  • : 写操作加排他锁。
  • 行为: 这是 PostgreSQL 的默认隔离级别。它通过 MVCC 确保读到的数据都是已提交的,但由于每个 SELECT 都会获取新快照,因此在同一个事务中多次读取同一行可能会看到不同的数据,导致“不可重复读”。

REPEATABLE READ

  • MVCC: 使用,且更严格。事务开始时会创建一个快照,在整个事务期间,所有读操作都只读取这个快照中的数据。
  • : 写操作加排他锁。
  • 行为: 这种模式确保了事务中的“可重复读”,因为其他事务的修改在当前事务提交前是不可见的。PostgreSQL 在这个级别下可以解决幻读,因为它会锁定范围内的行(通过一种类似于间隙锁的机制),从而阻止其他事务在该范围内插入新数据。这与 MySQL 的实现略有不同。

SERIALIZABLE

  • MVCC: 使用,但与锁结合。
  • : 读写操作都加锁。PostgreSQL 使用快照隔离(Serializable Snapshot Isolation, SSI)来实现这个级别。它比传统的基于锁的串行化效率更高。
  • 行为: 这是 PostgreSQL 最严格的隔离级别。它通过复杂的 SSI 算法,在不使用传统读写锁阻塞的情况下,确保事务的行为就如同串行执行一样。如果 SSI 检测到潜在的并发冲突,会回滚其中一个事务。这个机制比简单的读写加锁更精巧,因为它在保证最高隔离性的同时,尽可能地提高了并发性。