📚 DBMS
数据库事务实现原则
数据库事务的设计原则是 ACID,这是一个由四个英文单词首字母组成的缩写,代表了确保数据处理可靠性的四个核心特性。理解 ACID 原则对于设计和使用任何关系型数据库都至关重要。
原子性 (Atomicity)
原子性确保一个事务中的所有操作要么全部完成,要么全部不完成。如果事务中的任何一个操作失败,那么整个事务都会被回滚到开始前的状态,就好像这个事务从未发生过一样。
举例: 在银行转账中,从 A 账户扣款和向 B 账户加款是两个操作。原子性确保这两个操作要么都成功,要么都失败。如果从 A 账户扣款成功,但向 B 账户加款失败,那么扣款操作会自动撤销,确保 A 账户的余额保持不变。
一致性 (Consistency)
一致性确保事务在开始和结束时,数据库都保持一致状态。这意味着事务不会破坏数据库的完整性约束,例如主键约束、唯一性约束或外键约束。
举例: 如果一个表的 age 列被定义为 > 0,那么任何试图插入或更新 age 为负数的事务都将失败,从而维护了数据库的这个一致性规则。
隔离性 (Isolation)
隔离性确保多个并发事务互不干扰。每个事务都感觉自己是数据库中唯一在执行的操作。即使它们同时运行,一个事务的中间状态对其他事务来说是不可见的。
举例: 两个事务同时尝试修改同一条数据。隔离性确保它们不会互相覆盖对方的修改。数据库通过锁和 MVCC(多版本并发控制)等机制来保证隔离性。
持久性 (Durability)
持久性确保一旦事务提交,它所做的更改就会被永久保存在数据库中。即使系统发生故障(如断电或崩溃),这些更改也不会丢失。
举例: 当一个转账事务提交后,即使服务器立即崩溃,A 和 B 账户的余额变化也已经写入了硬盘,重启后仍然是正确的。数据库通过将数据写入日志文件或其他持久性存储来保证这一点。
事务隔离级别
事务隔离级别的对象
事务隔离级别(READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE)主要是 针对事务中 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 的主要目的并不是为了替代锁,而是为了解决读写冲突。它通过为每行数据创建多个版本,让不同的事务可以根据需要访问不同的版本,避免了读操作因等待写操作而阻塞。
-
提高并发性 这是 MVCC 最重要的作用。在没有 MVCC 的系统中,如果一个事务正在修改数据(写操作),那么任何想要读取同一数据的事务(读操作)都必须等待,直到写事务完成并释放锁。这会导致严重的性能瓶颈。MVCC 通过提供数据的旧版本,让读事务可以立即执行,而不需要等待,从而大大提高了数据库的并发处理能力。
-
实现事务隔离 MVCC 是实现
READ COMMITTED和REPEATABLE READ这两个关键隔离级别的主要技术。- 在
READ COMMITTED级别下,MVCC 确保每个SELECT语句都能看到最新的已提交数据版本,避免了“脏读”。 - 在
REPEATABLE READ级别下,MVCC 确保整个事务中的所有SELECT语句都看到同一个数据快照,避免了“不可重复读”的问题。
- 在
-
减少死锁 由于读操作不再需要加锁,也就不会因为等待其他事务所持有的锁而陷入僵局,这在很大程度上减少了死锁的发生。死锁主要发生在多个事务都尝试获取对方已经持有的锁时,而 MVCC 减少了读锁的需求,从而降低了死锁的概率。
简而言之,MVCC 是一种乐观的并发控制策略。它假设读写冲突不太可能发生,因此允许它们并发执行,只有当真正发生写写冲突时,才需要使用锁来解决。这种机制极大地优化了现代高并发数据库的性能。
PostgreSQL 的隔离级别实现
PostgreSQL 的四个标准事务隔离级别(READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE)都是通过 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 检测到潜在的并发冲突,会回滚其中一个事务。这个机制比简单的读写加锁更精巧,因为它在保证最高隔离性的同时,尽可能地提高了并发性。