MySQL事务隔离级别详解

MySQL事务隔离级别详解

个人公众号『码农札记』,欢迎关注,查看更多精彩文章。

简介: MySQL的事务隔离级别一共有四个,分别是读未提交、读已提交、可重复读以及可串行化。

四个特性ACID

原子性 (Atomicity)

事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。例如,如果一个事务需要新增 100 条记录,但是在新增了 10 条记录之后就失败了,那么数据库将回滚对这 10 条新增的记录。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。

一致性 (Consistency)

指事务将数据库从一种状态转变为另一种一致的的状态。事务开始前和结束后,数据库的完整性约束没有被破坏。例如工号带有唯一属性,如果经过一个修改工号的事务后,工号变的非唯一了,则表明一致性遭到了破坏。

隔离性 (Isolation)

要求每个读写事务的对象对其他事务的操作对象能互相分离,即该事务提交前对其他事务不可见。 也可以理解为多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。这指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。例如一个用户在更新自己的个人信息的同时,是不能看到系统管理员也在更新该用户的个人信息(此时更新事务还未提交)。
注:MySQL 通过锁机制来保证事务的隔离性。

持久性 (Durability)

事务一旦提交,则其结果就是永久性的。即使发生宕机的故障,数据库也能将数据恢复,也就是说事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。这只是从事务本身的角度来保证,排除 RDBMS(关系型数据库管理系统,例如 Oracle、MySQL 等)本身发生的故障。
注:MySQL 使用 redo log 来保证事务的持久性。

四种隔离级别

READ UNCOMMITTED(未提交读)

一个事务还没提交时,它做的变更就能被别的事务看到。
事务可以读取未提交的数据,这也被称为脏读(Dirty Read)

READ COMMITTED(读提交)

一个事务提交之后,它做的变更才会被其他事务看到。
一个事务从开启事务到提交事务之前,对其他事务都是不可见的,因此在同一个事务中的两次相同查询结果可能不一样。故这种隔离级别有时候也叫不可重复读(NONREPEATABLE READ)

REPEATABLE READ(可重复读)

一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。
当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。

SERIALIZABLE(可串行化)

顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当
出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

幻读

上文对脏读,不可重复读概念都进行了详细介绍,还有一种现象叫做幻读,概念如下:

幻读是针对数据插入(INSERT)操作来说的。假设事务A对某些行的内容作了更改,但是还未提交,此时事务B插入了与事务A更改前的记录相同的记录行,并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现好像刚刚的更改对于某些数据未起作用,但其实是事务B刚插入进来的,让用户感觉很魔幻,感觉出现了幻觉,这就叫 幻读

汇总

隔离级别 脏读 不可重复读 幻读 读未提交 可能 可能 可能 读提交 不可能 可能 可能 可重复读 不可能 不可能 可能 串行化 不可能 不可能 不可能

举个栗子

假设数据表 T 中只有一列,其中一行的值为 1,下面是按照时间顺序执行两个事务的行为。

mysql> create table T(c int) engine=InnoDB;
insert into T(c) values(1);
事务A 事务B 启动事务 查询得到值1 启动事务 查询值为1 将1更新成2 查询值为V1 提交事务B 查询值为V2 提交事务A 查询值为V3

若隔离级别是读未提交
则 V1 的值就是 2。这时候事务 B 虽然还没有提交,但是结果已经被 A 看到了。因此,V2、V3 也都是 2。
若隔离级别是读提交
则 V1 是 1,V2 的值是 2。事务 B 的更新在提交后才能被 A看到。所以, V3 的值也是 2。
若隔离级别是可重复读
则 V1、V2 是 1,V3 是 2。之所以 V2 还是 1,遵循的就是这个要求:事务在执行期间看到的数据前后必须是一致的。
若隔离级别是串行化
则在事务 B 执行“将 1 改成 2”的时候,会被锁住。直到事务 A 提交后,事务 B 才可以继续执行。所以从 A 的角度看, V1、V2 值是 1,V3 的值是 2。
可以看到在不同的隔离级别下,数据库行为是有所不同的。Oracle 数据库的默认隔离级别其实就是“读提交”,因此对于一些从 Oracle 迁移到 MySQL 的应用,为保证数据库隔离级别的一致,你一定要记得将 MySQL 的隔离级别设置为“读提交”。
配置的方式是,将启动参数 transaction-isolation 的值设置成 READ-COMMITTED。
你可以用 show variables 来查看当前的值。

mysql> show variables like 'transaction_isolation';
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+

总结来说,存在即合理,哪个隔离级别都有它自己的使用场景,你要根据自己的业务情况
来定。那什么时候需要“可重复读”的场景呢?我们来看一个数据校对逻辑的案例。
假设你在管理一个个人银行账户表。一个表存了每个月月底的余额,一个表存了账单明细。这时候你要做数据校对,也就是判断上个月的余额和当前余额的差额,是否与本月的账单明细一致。你一定希望在校对过程中,即使有用户发生了一笔新的交易,也不影响你的校对结果。
这时候使用“可重复读”隔离级别就很方便。事务启动时的视图可以认为是静态的,不受其他事务更新的影响。

小结

MySQL 的 InnoDB 引擎才支持事务,其中可重复读是默认的隔离级别。
读未提交和串行化基本上是不需要考虑的隔离级别,前者不加锁限制,后者相当于单线程执行,效率太差。
读提交解决了脏读问题,行锁解决了并发更新的问题。
本文主要介绍四种隔离级别和产生的问题,下一篇文章会深入解析隔离级别的实现原理MVCC和解决幻读的Next-Key 锁实现。
image