mysql文档--innodb中的重头戏--事务隔离级别!!!!--举例学习--底层原理探索

阿丹:

         之前的文章中解释并演示了innodb中的事务隔离级别的四种情况。本篇文章来对于事务隔离级别的实现来进行底层探索。下面的连接中演示了在各种隔离级别中会出现的现象。

mysql文档--innodb中的重头戏--事务隔离级别!!!!--举例学习--现象演示_一单成的博客-CSDN博客​​​​​​

 前提Mysql中有那些事务隔离级别:

在MySQL中,事务的隔离级别是由四个级别组成的,它们分别是:

  1. 读未提交(Read Uncommitted):最低的隔离级别,允许一个事务读取另一个事务尚未提交的数据。这种隔离级别可能导致脏读(Dirty Read)、不可重复读(Non-repeatable Read)和幻读(Phantom Read)问题。
  2. 读已提交(Read Committed):允许一个事务只读取另一个事务已经提交的数据。在这个级别下,避免了脏读问题,但仍可能出现不可重复读和幻读问题。
  3. 可重复读(Repeatable Read):保证在同一个事务中多次读取同一数据时,结果始终一致。这个级别通过在事务期间锁定读取的数据来实现。但是,它可能导致幻读问题,因为在一个事务执行过程中,其他事务可能会插入或删除满足某些条件的数据行。
  4. 串行化(Serializable):最高的隔离级别,完全遵循ACID原则,一个事务只能在另一个事务完成后进行,或者在前一个事务完成之前开始。这样可以避免所有形式的脏读、不可重复读和幻读问题。但是,这个级别可能导致较低的并发性能,因为它需要在事务之间完全同步。

在MySQL中,你可以通过以下语句设置事务隔离级别:

SET TRANSACTION ISOLATION LEVEL ;

其中可以是READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READSERIALIZABLE。默认的事务隔离级别是REPEATABLE READ

概述-事务隔离级别基于

事务隔离级别的底层实现主要基于数据库的日志系统和多版本并发控制(MVCC)策略。

Mysql--技术文档--MVCC(Multi-Version Concurrency Control | 多版本并发控制)_一单成的博客-CSDN博客

        对于MySQL来说,其内部存储引擎InnoDB广泛使用了MVCC来支持事务隔离级别。在MVCC中,每条数据都有一个唯一的事务ID(称为trx_id),每个事务在操作数据时都会记录这个事务ID。当一个事务在操作数据时,除了当前的最新数据外,还会在日志中留下一条记录,这条记录包含了事务ID和指向前一条记录(旧数据)的指针(db_roll_ptr)。当其他事务想要读取这些数据时,可以根据事务ID和指针来找到适合自己级别的事务隔离数据。

        不同的事务隔离级别在底层实现上的主要区别在于如何处理并发读取和修改数据的冲突。例如,在读已提交隔离级别下,只读取已经提交的事务的数据;在可重复读隔离级别下,通过在事务期间锁定读取的数据来保证多次读取结果的一致性;在串行化隔离级别下,通过将事务完全同步来避免脏读、不可重复读和幻读问题。

        同时,为了实现事务的原子性和持久性,InnoDB还引入了回滚日志(undo log)作为事务的底层实现机制。当事务对数据库进行修改时,InnoDB会生成对应的undo log,如果事务执行失败或调用了rollback,便可以利用undo log中的信息将数据回滚到修改之前的样子。Undo log属于逻辑日志,它记录的是SQL执行相关的信息。

        总的来说,数据库的事务隔离级别和底层实现主要基于日志系统和多版本并发控制策略,通过控制并发读取和修改数据的冲突来实现不同级别的事务隔离。同时,为了实现事务的原子性和持久性,还会借助回滚日志等机制进行底层实现。

读未提交(Read Uncommitted)

读未提交隔离级别在底层实现中,主要涉及到两个关键技术:多版本并发控制(MVCC)和事务隔离级别控制。

在读未提交隔离级别下,数据库系统采用MVCC机制来支持并发访问。每个事务在执行时都会创建一个唯一的事务ID,称为“快照ID”。当一个事务需要读取数据时,它不会对读取的数据行加锁,而是将这些数据行的原始版本(或快照)读入内存中。这些原始数据版本是根据事务开始时的数据状态快照而来的。在事务执行期间,如果有其他并发事务对数据进行修改,这些修改会被存储在一个新的数据版本中,并保留原始数据版本不变。当一个事务结束并提交时,将提交消息发送给其他事务。收到提交消息的事务会将共享缓存中读取的原始数据版本替换为新版本。这样,每个事务都可以在其自身的事务ID下执行,而不会影响其他事务读取或修改数据。

然而,由于读未提交隔离级别不进行数据行加锁或独占访问,它存在一些并发访问带来的问题。其中一个主要问题是脏读。脏读发生在在一个事务中读取到另一个事务未提交的数据。例如,一个事务读取了另一个事务修改的数据,但该事务最终并没有提交,那么读取到的数据就是脏数据。另一个问题是不可重复读。由于不加锁或独占访问,在同一个事务中多次读取同一行数据可能会得到不同的结果,因为其他事务可能在对数据进行修改。

最后,需要注意的是,虽然读未提交隔离级别可以实现并发访问,但由于没有严格的隔离控制,它的数据一致性和完整性问题是最严重的。因此,在许多实际应用中,更常用的隔离级别是读已提交或可重复读,它们可以避免脏读和不可重复读等问题,并提供更好的数据一致性保证。

读已提交(Read Committed)

当一个事务在"读已提交"隔离级别下执行时,它只能看到已经提交的事务所产生的数据版本,而不能看到其他未提交事务的数据修改。下面是一个例子来说明MVCC如何在"读已提交"隔离级别下运作,并根据undo log的作用进行比较:

假设有两个并发的事务T1和T2:

  1. 事务T1开始,并且读取了表中的一行数据(数据值为A)。

  2. 在T1读取数据期间,事务T2开始并且修改了该行的数据值为B,并提交了该事务。

  3. T1继续执行并尝试重新读取同一行的数据。

在这个例子中,T1读取行时,数据值为A(当前未提交的T2的修改不可见)。

而根据MVCC的流程,当T1读取行时,数据库会进行以下比较和处理:

  1. 当T1开始读取行时,数据库会检查该行的undo log,查找是否有T1修改该行的旧值。由于T1在这之前并没有对该行进行修改,所以undo log中没有相应的旧值,并且T1也不需要查找和比较。

  2. 当T1检查undo log后,数据库会进一步检查该行的数据版本。由于T2已经提交了并修改了该行的数据,T1当前读取的是最新提交的数据版本(数据值为B)。

这样,在"读已提交"隔离级别下,T1在读取行时只能看到已经提交的事务所产生的数据版本。数据库利用undo log记录每个事务的修改操作,并通过比较数据版本和旧值,确保读操作的一致性和隔离性。如果有其他并发的事务修改了同一行的数据,T1会检测到这些修改并读取最新的已提交数据版本。

可重复读(Repeatable Read)

read view 一致性视图

在可重复读(REPEATABLE READ)的事务隔离级别下,数据库通过"read view"(一致性视图)来实现隔离性和一致性。Read view是一个事务特定的快照,用于提供对某个时间点数据库状态的一致访问。

下面是read view一致性视图的底层工作流程:

  1. 事务开始:当一个事务开始时,数据库会为该事务创建一个read view,并记录事务开始时间戳。

  2. 数据版本号:每个数据库中的数据行都有一个版本号,它记录了该数据行最近一次被修改的事务ID和时间戳。

  3. 数据快照:在事务开始时,read view会记录当前数据库中所有数据行的版本号。这个快照代表了事务开始时数据库的一致状态。

  4. 读操作:在事务执行期间,如果有读操作发生,数据库会通过比较read view中记录的数据版本号和实际数据行的版本号来确定可见性。

  5. 可见性判断:当事务执行读操作时,如果数据行的版本号早于事务开始的时间戳,则表示该数据行在事务开始之前已经更新,事务不可见该修改,因为事务需要保持可重复读。如果数据行的版本号晚于等于事务开始的时间戳,则表示该数据行在事务开始之后被修改,事务可以读取该版本的数据。

  6. 数据一致性:通过read view中记录的数据版本号,事务可以保证在整个事务执行期间读取的数据保持一致,即使其他事务对数据进行了修改。这种机制避免了可见性问题,确保可重复读的隔离性和一致性。

通过使用read view一致性视图,数据库可以保证可重复读事务的一致性,并允许事务在事务开始时看到一个固定的、一致的数据库状态。这种机制对于需要保持数据的静态一致性的应用非常重要。

在可重复读级别,开启事务后,第一次执行sal查询时会生成生一致性视图,在事务提交之前这个都不会变更。这个视图由所有未提交的事务数组和已提交的最大事务id组成,

它长这样:trx_ids:

表示事务开启的时候,其它未提交的活跃的事务ID,这是一个集合,相对于当前事务一直是不可见的::

low_limit_id: 表示在生成一致性视图时,当前已经产生的最大事务ID+1(max_id)

up_limit_id:表示未提交事务组中最小的事务ID(min id)

mysql源码中如何判断事务是否是可见的?

在MySQL的源代码中,判断事务的可见性主要涉及到多版本并发控制(MVCC)的实现。以下是一个简化的示例代码块,展示了在MySQL中判断事务可见性的逻辑:

/* 文件: innobase\row\row0vers.c */

/* 函数: row_vers_set_and_print */

ulint row_vers_set_and_print(
    mtr_t*     mtr,
    btr_pcur_t* pcur,
    rec_t*     rec,
    dict_index_t* index,
    const ulint*    offsets) {
      ...
      /* 检查当前事务是否可以访问该版本 */
      if (UNIV_UNLIKELY
          (trx->state == TRX_STATE_FORCED_ROLLBACK)) {
        /* 当前事务处于强制回滚状态,无法访问任何数据版本 */
        return(NULL);
      }
      ...
      /* 检查该版本是否在当前事务的可见性范围内 */
      if (UNIV_UNLIKELY(trx->read_view != NULL) &&
          !read_view_sees_trx_id(trx->read_view, trx->id)) {
        /* 该版本在当前事务的读视图中不可见 */
        return(NULL);
      }
      ...
      /* 设置当前访问的版本为该版本 */
      vers = row_get_rec_vers(rec, index, offsets);
      ...
    }

/* 函数: read_view_sees_trx_id */

static ulint read_view_sees_trx_id(
    read_view_t*     read_view,
    trx_id_t         trx_id) {
  ...
  /* 检查读视图是否可见当前事务的ID */
  if (UNIV_UNLIKELY(trx_id >= read_view->up_limit_id)) {
    /* 当前事务的ID超过了读视图的可见范围 */
    return(FALSE);
  }
  ...
  /* 检查当前事务是否在读视图的可见范围内 */
  if (UNIV_UNLIKELY(trx->id >= read_view->up_limit_id)) {
    /* 当前事务已经提交或回滚,不在读视图的可见范围内 */
    return(FALSE);
  }
  ...
  /* 当前事务在读视图的可见范围内 */
  return(TRUE);
}

上述代码是在InnoDB存储引擎中判断事务可见性的部分逻辑。首先,通过检查当前事务的状态,排除处于强制回滚状态的事务,因为这种事务无法访问任何数据版本。接下来,使用read_view_sees_trx_id()函数来检查当前事务的ID是否在读视图的可见范围内。如果当前事务的ID超过了读视图的可见范围,或者当前事务已经提交或回滚,那么该版本对当前事务来说就是不可见的。如果可见,则设置当前访问的版本为该版本。需要注意的是,上述代码仅为简化示例,实际MySQL源代码中还包含其他与事务可见性相关的逻辑和细节。

你可能感兴趣的:(mysql,mysql,学习,数据库)