首批通过分布式安全可靠测评,为关键业务系统打造
事务隔离级别概述
更新时间:2025-12-27 09:20:28
概述
隔离级别用于描述事务并发执行时互相干扰的程度。ANSI/ISO SQL 标准(SQL 92)基于事务执行过程中必须避免的异常情况定义了四种隔离级别,隔离级别越高,事务间的相互影响越小,允许出现的异常情况越少;在最高的隔离级别可串行化(Serializable)中,不允许出现任何异常情况。 其中这些需要避免的异常情况包括:
脏读(Dirty Read):一个事务读到其他事务尚未提交的数据。
不可重复读(Non Repeatable Read):曾经读到的某行数据,再次查询发现该行数据已经被修改或者删除。例如:
select c2 from test where c1=1;第一次查询c2的结果为1,再次查询由于其他事务修改了c2的值,因此c2的结果为2。幻读(Phantom Read):在执行请求中,当再次执行相同的搜索条件时,结果集中读取到了另一个已提交事务新插入的满足条件的行。
隔离级别种类
ANSI/ISO SQL 标准(SQL 92)定义了四种隔离级别,这四种隔离级别如下:
读未提交(Read Uncommitted)
读已提交(Read Committed)
可重复读(Repeatable Read)
可串行化(Serializable)
四种隔离级别以及它们对上述异常情况的容忍程度如下所示:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交 | 可能 | 可能 | 可能 |
| 读已提交 | 不可能 | 可能 | 可能 |
| 可重复读 | 不可能 | 不可能 | 可能 |
| 可串行化 | 不可能 | 不可能 | 不可能 |
OceanBase 数据库隔离级别
OceanBase 数据库目前支持了以下几种隔离级别:
Oracle 模式
读已提交(Read Committed)
可串行化(Serializable)
MySQL 模式
读未提交(Read Uncommitted)
读已提交(Read Committed)
可重复读(Repeatable Read)
可串行化(Serializable)
该隔离级别类似 Oracle 数据库的 Serializable,并非严格意义上的 Serializable。
OceanBase 数据库默认的隔离级别为读已提交(Read Committed)。
实际在 OceanBase 数据库中只实现了两种隔离级别,即读已提交和可串行化,当用户指定可重复读的隔离级别时,实际使用的是可串行化;与此同时,当用户指定读未提交的隔离级别时,实际使用的是读已提交。也就是说,OceanBase 数据库的可重复读的隔离级别更加严格,不会出现幻读的异常情况。OceanBase 数据库的读已提交不会出现脏读的异常情况,可能会出现不可重复读和幻读的异常情况;而可串行化不会出现脏读、不可重复读以及幻读的异常情况。
读已提交
在 OceanBase 数据库的读已提交隔离级别下,每条 SELECT 语句执行时仅能够读到在此之前所有已经提交事务的数据,而不会读到在语句执行过程中新提交或被并发事务修改的数据,就像是在每条语句执行之前获取了当前数据库的最新快照。快照中只记录了已提交的数据,因此不会出现脏读的异象。但由于每条语句执行前会获取新的快照,在同一个事务中连续的两条 SELECT 语句有可能看到不同的数据,即读已提交隔离级别下不能避免不可重复读和幻读的异常情况。
对于 UPDATE、DELETE、SELECT FOR UPDATE 等更新操作,它们在搜索目标行时的行为与 SELECT 相同,即只能找到在语句开始前已经提交的行版本,如果该版本不满足更新操作的谓词条件,则直接跳过该行,如果满足条件,才尝试更新该行。然而,当前事务(简称事务 A)在尝试更新一个满足谓词条件的目标行时,该行可能已经被另一个并发事务(简称事务 B)更新。在这种情况下,若事务 B 还没有结束,事务 A 需要等待事务 B 的提交或回滚。如果事务 B 回滚,事务 A 可以继续更新目标行;如果事务 B 提交,事务 A 将重新执行该语句,从而重新获取语句快照,以读到事务 B 更新后的版本,如果该版本依旧满足谓词条件,则在此基础上再进行更新。
可重复读或可串行化
在 OceanBase 数据库的可串行化(或可重复读)隔离级别下,事务的第一条语句会获取当前数据库的快照作为事务快照,后续 SELECT 语句都会基于事务快照读取数据,只能读到在事务快照之前所有已经提交事务的数据,而不会读到在事务执行过程中新提交或被并发事务修改的数据。由于每条语句使用同一个事务快照,所以事务内总能看到一致的数据,不会出现不可重复读和幻读的异常情况。
对于 UPDATE、DELETE、SELECT FOR UPDATE 等更新操作,它们在搜索目标行时的行为与 SELECT 相同,即只能找到在获取事务快照前已经提交的行版本,如果该版本不满足更新操作的谓词条件,则直接跳过该行,如果满足条件,才尝试更新该行。然而,当前事务(简称事务 A)在搜索到一个目标行时,该行可能已经被另一个并发事务(简称事务 B)更新。在这种情况下,若事务 B 还没有结束,事务 A 需要等待事务 B 的提交或回滚。如果事务 B 回滚,事务 A 可以继续更新最初找到的行;如果事务 B 提交,事务 A 不能基于旧的快照进行更新,否则会出现丢失更新(Lost Update)的情况,因此事务 A 只能进行回滚,此时 OceanBase 数据库会返回下列错误信息:
Oracle 模式:
ORA-08177: can't serialize access for this transactionMySQL 模式:
ERROR 6235 (25000): can't serialize access for this transaction
业务层需要考虑到事务可能会因为写冲突而回滚,并准备事务重试逻辑。如果事务复杂,重试代价较大,且业务并不要求事务所有语句看到一致的数据情况,建议使用读已提交的隔离级别。
OceanBase 数据库可串行化隔离级别的限制
在 SQL 标准中,可串行化隔离级别只需要避免脏读、不可重复读和幻读的异常情况,但是可串行化的严格定义为:任何两个成功提交的并发事务按顺序执行,一个事务在另一个事务之后,即事务的并行执行结果需要和某种串行执行的结果相同。与 Oracle、PostgreSQL 9.0 及之前的版本类似,OceanBase 数据库的可串行化隔离级别下并不能保证严格的可串行化,即事务执行的结果可能不与任何串行执行模式的结果相同。其中写偏斜(Write Skew)是一个经典例子: 假设存在表 T1(num int) 和 T2(num int),两个表初始都没有数据。此时在事务 1(Trx1)和事务 2(Trx2)中按照以下顺序执行命令,
Trx1 Trx2
BEGIN;
INSERT INTO T2 SELECT COUNT(*) FROM T1;
BEGIN;
INSERT INTO T1 SELECT COUNT(*) FROM T2;
COMMIT;
COMMIT:
由于 Trx1 和 Trx2 获取的快照中两个表的 COUNT 都为 0,最终表 T1 和表 T2 中都会插入 num=0 的一行,而假设事务 1 和事务 2 串行执行,无论是 Trx1->Trx2 还是 Trx2->Trx1,最终两个表应该会分别插入 num=0 和 num=1。OceanBase 数据库目前无法保证严格可串行化的原因在于其读操作不会上锁,读写不互斥,并且也没有在事务提交时检查读写冲突是否成环。在大部分现实应用场景下,只要不出现脏读、幻读和不可重复读就能够满足业务的需求,如果业务一定需要严格的可串行化,可以显式地为读操作加锁,如 SELECT FOR UPDATE。
注意
串行化隔离级别下,开启事务后,会保留事务快照,事务提交后快照才会回收。因此,串行化隔离级别下,需要注意及时提交事务,否则可能遇到磁盘占用变大或者读取越来越慢的问题。
OceanBase 数据库隔离级别与其他数据库对比
| 数据库 | 读未提交(Read Uncommitted) | 读已提交(Read Committed) | 可重复读(Repeatable Read) | 可串行化(Serializable) |
|---|---|---|---|---|
| OceanBase | 仅支持语法 | 支持,与 SQL 标准一致 | 支持,且不存在幻读 | 支持,但不保证严格可串行化 |
| Oracle | 不支持语法 | 支持,与 SQL 标准一致 | 不支持语法 | 支持,但不保证严格可串行化 |
| MySQL | 支持,有可能读到脏数据 | 支持,与 SQL 标准一致 | 支持,且不存在幻读 | 支持,可以保证严格可串行化 |
| PostgreSQL 9.1 之前版本 | 支持语法,但实际是 Read Committed | 支持,与 SQL 标准一致 | 支持,且不存在幻读 | 支持,但不保证严格可串行化 |
| PostgreSQL 9.1 及之后版本 | 支持语法,但实际是 Read Committed | 支持,与 SQL 标准一致 | 支持,且不存在幻读 | 支持,可以保证严格可串行化 |