事务隔离级别有哪些?怎么实现的?

2.4.1. 事务隔离级别是怎么实现的

InnoDB 引擎通过什么技术来保证事务的这四个特性的呢?

  • 持久性是通过 redo log (重做日志)来保证的;
  • 原子性是通过 undo log(回滚日志) 来保证的;
  • 隔离性是通过 MVCC(多版本并发控制) 或锁机制来保证的;
  • 一致性则是通过持久性+原子性+隔离性来保证;

MySQL 在「可重复读」隔离级别下,可以很大程度上避免幻读现象的发生,使用「序列化」隔离级别会影响性能,所以很少使用「序列化」隔离级别来避免幻读现象的发生。

「可重复读」通过两种方式很大程度避免了幻读:

  1. 快照读(普通select语句),通过MVCC方式解决幻读,该隔离级别下事务过程中的数据和事务启动时的数据是一致的,即使过程中有其他事务插入数据,也是查询不出来的,所以很好的避免了幻读问题。
  2. 当前读(select... for update等语句),通过 next-key lock(记录锁+间隙锁)方式解决幻读,当执行 select ... for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。

四种隔离级别具体是如何实现的呢?

  • 「读未提交」隔离级别的事务:可以读到未提交的事务修改的数据,所以直接读取最新的数据;
  • 「序列化」隔离级别的事务:通过加读写锁的方式来避免并行访问;
  • 「读提交」和「可重复读」隔离级别的事务:通过 Read View 来实现的,区别在于创建 Read View (可理解为数据快照)的时机不同,「读提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View,而「可重复读」隔离级别是「启动事务时」生成一个 Read View,然后整个事务期间都在用这个 Read View。

两种开启事务的命令,事务的启动时机是不同的:

  • 执行了 begin/start transaction 命令后,执行了第一条 select 语句,才是事务真正启动的时机;
  • 执行了 start transaction with consistent snapshot 命令,就会马上启动事务。
2.4.1.1. Read View 在 MVCC 里如何工作的?

Read View有4个重要的字段:

  • m_ids :创建 Read View 时,当前数据库中「活跃事务(启动了但没提交)」的事务 id 列表,注意是一个列表。
  • min_trx_id :创建 Read View 时,当前数据库中「活跃事务」中事务 id 最小的事务,也就是 m_ids 的最小值。
  • max_trx_id :不是 m_ids 的最大值,而是创建 Read View 时当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1;
  • creator_trx_id :指的是创建该 Read View 的事务的事务 id。

聚集索引中的两个隐藏列:

InnoDB 存储引擎的数据库表,它的聚集索引记录中都包含下面两个隐藏列:

  1. trx_id,保存对某条聚集索引记录改动的事务id
  2. roll_pointer,每次对某条聚集索引记录进行改动时,都会把旧版本的记录写入到 undo 日志中,然后这个隐藏列是个指针,指向每一个旧版本记录,可以通过它找到修改前的记录。

创建Read View 后,记录中的 trx_id 划分这三种情况:

一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况:

  • 如果记录的 trx_id(事务id) 值小于 Read View 中的 min_trx_id(未提交事务) 值,表示这个版本的记录是在创建 Read View 已经提交的事务生成的,所以该版本的记录对当前事务可见
  • 如果记录的 trx_id 值大于等于 Read View 中的 max_trx_id 值,表示这个版本的记录是在创建 Read View 才启动的事务生成的,所以该版本的记录对当前事务不可见
  • 如果记录的 trx_id 值在 Read View 的 min_trx_id 和 max_trx_id 之间,需要判断 trx_id 是否在 m_ids (启动未提交事务)列表中:
    • 如果记录的 trx_id m_ids 列表中,该事务还没提交,所以该版本的记录对当前事务不可见
    • 如果记录的 trx_id 不在 m_ids列表中,表示该事务已经被提交,所以该版本的记录对当前事务可见

这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)。

2.4.1.2. 可重复读是如何工作的?(快照读如何避免幻读?)

可重复读隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View

启动两个事务,A和B,当前有一条记录,事务B读取了这条记录,该记录的事务id比当前数据库中活跃事务的最小值还小,说明是在事务B启动前就提交了,所以能够查询到。

现在事务A修改了这条记录,MySQL记录相应的undo log,旧版本和新版本记录通过链表连接起来,将当前的事务id+1。

事务B第二次读取这条记录,当前的事务id在活跃事务的列表内,说明是未提交事务修改的所以不读取这个版本的记录,通过undo log找到小于事务B中活跃事务最小值的第一条记录。

事务A提交事务后,当前隔离级别是可重复读,事务B在读取记录时,还是基于启动事务时的Read View,所以读取的数据依然是事务A修改前的数据。

2.4.1.3. 读提交是如何工作的?

读提交隔离级别是在每次读取数据时,都会生成一个新的 Read View

在读提交隔离级别下,事务每次读数据时都重新创建 Read View,在事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。

2.4.1.4. MySQL可重复读隔离级别,完全解决幻读了吗?

当前读如何避免幻读?

除了普通查询是快照读,其他都是当前读,比如 update、insert、delete,这些语句执行前都会查询最新版本的数据,然后再做进一步的操作。

Innodb 引擎为了解决「可重复读」隔离级别使用「当前读」而造成的幻读问题,就引出了间隙锁。

事务 A 执行了上面这条锁定读语句后,就对表中的记录加上 id 范围为 (2, +∞] 的 next-key lock(next-key lock 是间隙锁+记录锁的组合)。

事务 B 在执行插入语句的时候,判断到插入的位置被事务 A 加了 next-key lock,于是事物 B 会生成一个插入意向锁,同时进入等待状态,直到事务 A 提交了事务。这就避免了由于事务 B 插入新记录而导致事务 A 发生幻读的现象。

可重复读隔离级别下虽然很大程度上避免了幻读,但是还是没有能完全解决幻读

对于快照读, MVCC 并不能完全避免幻读现象。当事务 A 更新了一条事务 B 插入的记录,这条新纪录的trx_id隐藏列的值会变成事务A的事务id,所以就发生幻读。

对于当前读,如果事务开启后,并没有执行当前读,而是先快照读,然后这期间如果其他事务插入了一条记录,那么事务后续使用当前读进行查询的时候,就会发现两次查询的记录条目就不一样了,所以就发生幻读。避免这类特殊场景下发生幻读的现象的话,就是尽量在开启事务之后,马上执行 select ... for update 这类当前读的语句,因为它会对记录加 next-key lock,从而避免其他事务插入一条新记录。

  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: MySQL的事务隔离级别包括:读未提交(READ UNCOMMITTED)、读提交(READ COMMITTED)、可重复读(REPEATABLE READ)和串行化(SERIALIZABLE)。MySQL实现事务隔离级别的主要手段是使用MVCC(多版本并发控制)技术,具体来说就是利用时间戳或版本号来追踪数据的变化,从而实现各个事务之间的隔离。 ### 回答2: 数据库事务隔离级别主要包括读未提交、读提交、可重复读和串行化四种级别。 1. 读未提交(Read Uncommitted):事务可以读取其他事务未提交的数据,可能导致脏读(Dirty Read)问题。 2. 读提交(Read Committed):事务只能读取其他事务已提交的数据,避免了脏读问题,但可能出现不可重复读(Non-repeatable Read)问题。 3. 可重复读(Repeatable Read):事务在整个过程中能够多次读取到相同的数据快照,避免了脏读和不可重复读问题。 4. 串行化(Serializable):事务执行时对数据加锁,保证了数据的完全隔离性,但可能导致并发性能下降。 MySQL的事务隔离级别通过锁机制来实现。为了实现不同的隔离级别,MySQL使用了多版本并发控制(MVCC)机制。MVCC根据事务开始时间来判断可见性,每个事务启动时会分配一个唯一的事务ID,用来标识该事务数据库会把此时版本号小于或等于该事务ID的数据行视为可见。 对于不同的隔离级别,MySQL采取了以下策略: - 读未提交:对被修改的数据行不加任何锁,事务启动后可以读取到其他未提交的事务的数据。 - 读提交:对于读操作,采用共享锁(Shared Lock),即只允许读取其他已提交事务的数据,不允许读取未提交的事务数据。 - 可重复读:在事务期间,对于读操作,采用快照读(Snapshot Read),即读取事务开始时数据的一个快照,事务读到的数据保持一致性。 - 串行化:对于读和写操作,采用行级排他锁(Row-Level Exclusive Lock),即对读取和修改的数据行都加锁,保证读写的完全隔离性。 总结来说,MySQL的事务隔离级别是通过锁机制和MVCC机制来实现的,通过对数据行的锁定和版本控制,保证了不同事务隔离级别下数据的可见性和一致性。 ### 回答3: 事务隔离级别是指数据库系统对并发操作的处理方式以及保证事务之间的隔离性程度。常见的事务隔离级别有读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。 MySQL的事务隔离级别通过锁定机制来实现。具体来说,Mysql使用两种主要的锁定方式:共享锁(Shared Locks)和排他锁(Exclusive Locks)。 对于读未提交级别,Mysql不使用任何锁定机制,允许事务读取未提交的数据。这种隔离级别的性能较高,但会导致脏读(Dirty Reads)的问题。 对于读已提交级别,Mysql使用共享锁定。即一个事务读取数据时,该数据会被加上共享锁,直到该事务提交后才会释放锁。这种隔离级别可以避免脏读的问题。 对于可重复读级别,Mysql使用共享锁和排他锁。一个事务在读取数据时,会加上共享锁。如果在该事务还未提交之前,其他事务想要修改该数据,则会被阻塞。这种隔离级别可以避免脏读和不可重复读(Non-repeatable Reads)的问题。 对于串行化级别,Mysql使用排他锁。即一个事务在读取数据时,会加上排他锁,其他事务无法同时读取或修改该数据。这种隔离级别可以避免脏读、不可重复读和幻读(Phantom Reads)的问题。 总之,Mysql的事务隔离级别是通过锁定机制来实现的,不同的隔离级别会使用不同的锁定方式来保证事务之间的隔离性和数据的一致性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值