MySql(Innodb)事务隔离级别

ACID

回顾事务的ACID特性,ACID分别是一下四个词的缩写:

  • Atomicity(原子性)
  • Consistency(一致性)
  • Isolation(隔离性)
  • Durability(持久性)

事务隔离性

  “隔离性还有其他的称呼,如并发控制(concurrency control)、可串行化(serializability)、锁(locking)等。事务的隔离性要求要求每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见,通常这使用锁来实现。当前数据库系统中都提供了一种粒度锁(granular lock)的策略,允许事务仅锁住一个实体对象的子集,以此来提高事务之间的并发度。”——引自《MySql技术内幕 InnoDB 存储引擎》

  简单的说,事务隔离性就是指在并发情况下,事务能够相互隔离,不彼此影响,避免一些“不良反应”。

事务并发

在并发状态时,如果不控制好事务,如果事务没有隔离性,会有哪些“不良反应”?

  • 脏读(Dirty read):

    1. 事务 A 在操作数据时,把数据 a 改成 b,但是事务未提交;
    2. 此时数据 a 是之前的数据,是有效的。数据 b 未提交,是无效的,如果在事务处理期间发生错误,数据 b 会回滚到 a;
    3. 如果此时事务 B 访问了数据,得到的是 b,那么就发生了脏读,读到了“脏数据” b。
  • 不可重复读(Unrepeatableread):

    1. 因为业务需要,事务 A 在处理事务过程中,需要多次读取数据 a=1,而且每次读取的结果需要一样,即 a 时刻保持1;
    2. 事务 B 在事务 A 的处理过程中,修改了 a 的值,a=2;

    事务 A 对于数据 a,不能保证重复读取时的值保持一致,这就是不可重复读。

  • 幻读(Phantom):

    1. 事务 A 读取满足条件的某些数据,返回了 5 条数据;
    2. 事务 B 插入了新的满足同样条件的数据;
    3. 事务 A 因为业务需要再次读取数据,此次返回了 6 条数据,多出的数据就好像是“幻影”(Phantom)一样;

    可以看到,“幻读”和“不可重复读”有点类似,两者都是在事务 A 过程中需要读取多次数据,但是期间数据被另一个事务 B 篡改,导致多次读到的数据不一致。
    不同点在于,“不可重复读”是数据“值”的不一致,而“幻读”是数据记录的“数量”的不一致。

  • 丢失修改(Lost to modify):

    1. 事务 A 和 事务 B 都要修改数据 num=2;
    2. 事务 A 和 事务 B 同时读到 num=2;
    3. 事务 A 把数据 num 减一变成 num=1;
    4. 事务 B 也把数据 num 减一,但确是在读到的初始值上减一(num=2-1=1),而不是在事务 A 的基础上减一(num=1-1=0)。

    这样,事务 A 的操作结果被事务 B 的操作结果覆盖了,相当于事务 A 对数据的修改“丢失”了。

事务隔离级别

  事务的隔离级别,就是能够避免在事务并发条件下产生的“不良反应”的能力,就是事务能隔绝其它事务的影响的能力。
  事务的隔离级别越高,规避风险的能力也越强,事务在并发环境下出现的“不良反应”的概率越低。
  “隔离级别越低,事务请求的锁越少或保持锁的时间就越短。”——《xx内幕InnoDB引擎》
  “事务的隔离级别”,表示的是一种规范、约束,不同的数据库的具体实现都不一致,不同数据库采取不同的措施来保证事务达到对应的隔离级别。

MySql中包含四级的事务隔离级别,如下:

读未提交

  READ-UNCOMMITTED,简写“RU”。应该扩展为“read uncommitted record”,即“可读未提交的记录”。表示在一个事务未提交时,另一个事物被允许读取相关的数据。
  这是最低的隔离级别,相当于啥也没干,自然地,上述的“不良反应”在这种情况下都会发生。

读已提交

  READ-COMMITTED,“read committed record”,“只能读已提交的记录”,简写“RC”。在一个事务提交后,另一个才被允许读取相关数据。
  在这种级别下,当一个事务修改了数据,产生了“脏数据”,而该事务又尚未提交时,另一个事务不被允许去读取相关的数据,自然地这就解决了“脏读的问题”。

可重复读

  REPEATABLE-READ,简写“RR”。这种隔离级别下,需要保证一个事务内多次读取某个记录,其数据都是一致的。该级别杜绝了“脏读”和“不可重复读”的问题。

序列化

  SERIALIZABLE,在这种隔离级别下,事务串行执行,能够解决“脏读”、“不可重复读”、“幻读”、“丢失修改”的问题,当然,因为是串行执行,所以它的性能是最低的。

MySQL中这四种隔离级别的字符、数字编号表示如下:

表格的传送门——mysql 幻读的详解、实例及解决办法

拓展

幻读的理解

  接着上面贴出来的链接 “mysql 幻读的详解、实例及解决办法” 讲。

以上这一段关于“幻读”的理解,已经被复制粘贴到各个平台,什么CSDN、博客园等等,个人觉得这段话有点儿误导的嫌疑。
  “事务A执行两次select得到不同数据集”这种情况应该是属于“幻读”而不是“不可重复读”。
  在 MySQL(InnoDB) 默认的隔离级别 “REPEATABLE-READ” 下,两次 select 得到的结果集是一样的,不会出现典型的“幻读”现象,这是因为 InnoDB 的 “REPEATABLE” 隔离级别是通过 “MVCC” 实现的。
  简单介绍“MVCC”。“MVCC” 即 “Multi-Version Concurrency Control”(多版本并发控制)。大概的思想是——对每条数据添加“版本号”,当数据修改后,“版本号”也相应地进行“更新”,在处理数据时基于“版本号”进行处理。 在事务 A 中,如果同一条记录两次读取的“版本号”不一致,表示事务 A 在这两次读取记录的过程中,记录被其它事务修改了,这时事务 A 考虑回滚数据或做其它操作。
  不同数据库对“MVCC”的具体实现都不一样,有乐观的也有悲观的,在 MySQL InnoDB 引擎中 “MVCC” 是乐观的,与“CAS(CompareAndSwap)”相类似。
  在《高性能MySQL》中有对 “MVCC” 的较为详细的说明:

InnoDB 的 MVCC,是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存行的过期时间(或删除时间)。当然存储的并不是实际的时间值,而是系统版本号(system version number)。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。下面看一下在REPEATABLE READ 隔离级别下,MVCC 具体是如何操作的。

  • SELECT: InnoDB 会根据以下两个条件检查每行记录:

    1. InnoDB 只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
    2. 行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。

    只有符合上述两个条件的记录,才能返回作为查询结果。

  • INSERT: InnoDB 为新插入的每一行保存当前系统版本号作为行版本号。

  • DELETE: InnoDB 为删除的每一行保存当前系统版本号作为行删除标识。

  • UPDATE: InnoDB 为插入一行新记录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。

InnoDB的幻读

  “InnoDB 存储引擎默认支持的隔离级别是 REPEATABLE READ,但是与标准 SQL 不同的是,InnoDB 存储引擎在 REPEATABLE READ 事务隔离级别下,使用 Next-Key Lock 锁的算法,因此避免幻读的产生。这与其他数据库系统(如 Microsoft SQL Server 数据库)是不同的。所以说,InnoDB 存储引擎在默认的 REPEATABLE READ 的事务隔离级别下已经能完全保证事务的隔离性要求,即达到 SQL 标准的 SERIALIZABLE 隔离级别。”——《MySQL技术内幕InnoDB存储引擎》

  在 InnoDB 的 REPEATABLE READ 隔离级别下,基于 MVCC 方法实现的“普通读”或者说是“快照读”、“非锁定读”避免了“幻读”的现象;在“当前读”的情况下,“幻读”的解决就是依靠上述的“Next-Key Lock”算法。在 InnoDB 默认的 REPEATABLE READ 隔离级别下一般是不会出现幻读的。

相关资料

原文地址:https://www.cnblogs.com/life-of-coding/p/13160568.html