隔离级别指定了事务的各部分如何以及何时可以被其他事务看到。换句话说,隔离级别描述了将事务与其他并发事务隔离的程度以及执行过程中可能发生哪些种类的异常(anomaly)。实现隔离需要付出一定的代价:为了防止其他事务读到不完整或临时的写入,我们需要额外的协调和同步机制,而这会对性能产生负面影响。
SQL标准描述了执行并发事务期间可能发生的读异常:脏读(dirty read)、不可重复度(nonrepeatable read,也称为模糊读fuzzy read)和幻读
读异常 | 概念 | 示例描述 |
---|---|---|
脏读 | 一个事务能读到其他事务未提交的更改 | 事务T1更新了一条用户记录的地址字段事务T2在T1提交前就读到了已被更新的地址 |
不可重复读 | 也称为模糊度(fuzzy read),同一事务两次查询同一行却得到不同的结果。 | 事务T1读取一行,然后事务T2修改该行并提交了这一更改。如果T1在完成执行之前再次查询同一行,则结果将会和前一次查询不同。 |
幻读 | 事务两次查询同样的行集合却得到不同的结果。它与不可重复读类似,但仅适用于范围查询。 |
也存在具有类似语义的写异常:丢失更新(Lost Update),脏写和写偏斜。
写异常 | 示例描述 | |
---|---|---|
丢失更新 | 发生在事务T1和T2同时尝试更新V的值时。T1和T2读取V的值。T1更新V并提交,之后T2也更新V并提交。由于这两个事务不知道彼此的存在,所以如果允许二者都提交,则T1的结果将被T2的结果覆盖,T1的更新。 | |
脏写 | 某个事务拿到了一个未提交的值(即脏读),对其进行修改并保存也就是说,事务结果来自从未提交过的值。 | |
写偏斜 | 各个单独的事务都遵守要求的约束,但它们的组合却违反了这些约束。 | 事务T1和T2修改两个账户A1和A2的值。最初A1有100元,A2有150元。账户的值可以为负,只要两个账户之和为非负数就行,即要满足A1+A2≥0。T1和T2分别尝试分别从A1和A2取出200元。在两个事务开始时,A1+A2=250元,因此共有250元可用。两个事务都认为它们没有违反约束,可以提交。提交后,A1为-100元,A2为-50元,这显然违反了账户之和为非负数的要求 |
隔离级别和允许的异常
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read uncommitted) | 允许 | 允许 | 允许 |
读已提交(read commited) | 不允许 | 允许 | 允许 |
可重复读(repeatable read) | 不允许 | 不允许 | 允许 |
可串行化(serializability) | 不允许 | 不允许 | 不允许 |
ACID中的隔离性意味着可串行化,实现可串行化需要协调,也就是说,并发执行的事务必须进行协调以确保遵守约束,并在发生冲突时强加某一串行顺序。一些DB采用快照隔离,在快照隔离(snapshot isolation)中,一个事务可以观察到所有在该事务开始前已提交的事务所做的状态更改。每个事务都会获取一个数据快照,并在快照上执行查询。快照在事务执行期间不会发生变化。只有当事务中修改的值没有被其他事务修改时,才可以提交事务,否则它将被中止并回滚。
- 如果两个事务试图修改同一个值,则只有其中一个事务能够提交,这会防止丢失更新异常。例如,事务T1和T2都尝试修改V。它们从快照读取V的当前值。首先尝试提交的事务会成功提交,而另一个事务则必须中止。失败的事务将会重试,而不是覆盖已修改的值。
- 快照隔离中可能发生写偏斜异常:如果两个事务分别读取本地状态、修改不同的记录并且遵守本地的约束。
Postgres的隔离级别
当多个事务同时在数据库中运行时,并发控制是一种用于维持一致性与隔离性的技术。PostgreSQL使用一种MVCC的变体,叫作快照隔离(Snapshot Isolation, SI)。PostgreSQL通过可见性检查来实现SI,即新数据对象被直接插入相关表页中,读取对象时,根据可见性检查规则,为每个事务选择合适的对象版本作为响应。
SI中不会出现在ANSI SQL-92标准中定义的三种异常,分别是脏读、不可重复读和幻读。但 SI无法实现真正的可串行化,因为在 SI中可能会出现串行化异常,例如写偏差和只读事务偏差。需要注意的是,ANSI SQL-92标准中可串行化的定义与现代理论中的定义并不相同。为了解决这个问题,PostgreSQL 从9.1版本之后添加了可串行化快照隔离(Serializable Snapshot Isolation, SSI), SSI可以检测串行化异常,并解决这种异常导致的冲突。因此,9.1版本之后的PostgreSQL提供了真正的SERIALIZABLE隔离等级。
隔离级别 | 脏读 | 不可重复读 | 幻读 | 串行化异常 | |
---|---|---|---|---|---|
读未提交 | SQL标准允许,PG中不可能出现 | 可能 | 可能 | 可能 | 实际上不存在,因为PG中不存在脏读 |
读已提交 | 不可能 | 可能 | 可能 | 可能 | 事务在每次执行SQL时获取快照 |
可重复读 | 不可能 | 不可能 | SQL标准允许,PG中不可能出现 | 可能 | 事务只会在执行第一条命令时获取一次快照 |
可串行化 | 不可能 | 不可能 | 不可能 | 不可能 | 事务只会在执行第一条命令时获取一次快照 |
在9.0及更低的版本中,可重复读被当作可串行化(它满足了ANSI SQL-92标准),但9.1版本中SSI的实现可意见检查串行化异常,并解决串行化异常导致的冲突,实现了真正的SERIALIZABLE隔离等级。PostgreSQL对DML(SELECT、UPDATE、INSERT、DELETE等命令)使用SSI,对DDL(CREATE TABLE等命令)使用2PL。
参考资料
- 《数据库系统内幕》
- 《PostgreSQL指南》