SQL 标准定义了 4 类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。

一、SQL 事务隔离级别说明

1.1 Read Uncommitted(读取未提交内容)

在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。

1.2 Read Committed(读取提交内容)

这是大多数数据库系统的默认隔离级别(但不是 MySQL 默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的 commit,所以同一 select 可能返回不同结果。

1.3 Repeatable Read(可重读)

这是 MySQL 的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的 “幻影” 行。InnoDB 和 Falcon 存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。

1.4 Serializable(可串行化)

这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

二、事务隔离带来的问题

这四种隔离级别采取不同的锁类型来实现,若读取的是同一个数据的话,就容易发生问题。例如:

2.1 脏读(Drity Read)

一个事务读取到另一事务未提交的更新数据。当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中(这个数据在有可能会回滚),这时,另外一个事务也访问这个数据,然后使用了这个数据。

2.2 不可重复读 (Non-repeatable read)

在一个事务内,前后两次读到的数据是不一样。在 T1 事务两次读取同一数据之间,T2 事务对该数据进行了修改,就会发生 T1 事务中的两次数据读取不一样的结果。相反, 可重复读:在同一事务中多次读取数据时,能够保证所读数据一样,也就是后续读取不能读到另一事务已提交的更新数据。

2.3 幻读 (Phantom Read)

指当事务不是独立执行时发生的一种现象,例如:T1 事务对表中的 “全部数据行” 进行了修改,同时 T2 事务向表中插入了一行 “新数据”,操作 T1 事务的用户发现表中还存在没有修改的数据行,就好象发生了幻觉一 样。一般解决幻读的方法是增加范围锁 RangeS,锁定检锁范围为只读,这样就避免了幻读。

2.4 不可重复读和幻读的异同

  • 两者都表现为两次读取的结果不一致
  • 不可重复读是由于另一个事务对数据的更改所造成的,第二次读到了不一样的记录
  • 幻读是由于另一个事务插入或删除引起的,第二次查询的结果发生了变化
  • 对于不可重复读,只需要锁住满足条件的记录
  • 对于幻读,要锁住满足条件及其相近的记录

三、MySQL 隔离级别

隔离级别 脏读(Dirty Read) 不可重复读(NonRepeatable Read) 幻读(Phantom Read)
未提交读(Read uncommitted) 可能 可能 可能
已提交读(Read committed) 不可能 可能 可能
可重复读(Repeatable read) 不可能 不可能 可能
可串行化(SERIALIZABLE) 不可能 不可能 不可能

四、MySQL 事务隔离级别设置

4.1 InnoDB 默认是可重复读的(REPEATABLE READ)

修改全局默认的事务级别,在 my.inf 文件的 [mysqld] 节里类似如下设置该选项(不推荐)

transaction-isolation = {READ-UNCOMMITTED | READ-COMMITTED | REPEATABLE-READ | SERIALIZABLE}

4.2 改变单个会话或者所有新进连接的隔离级别(推荐使用)

SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}

4.3 查询全局和会话事务隔离级别方法

#查询全局的事务隔离级别
SELECT @@global.tx_isolation;
#查询当前会话的事务级别
SELECT @@session.tx_isolation;