Mysql事务隔离级别及其底层原理

2023-12-13 05:30:55

理解事务隔离级别

MySQL 是一种常用的关系型数据库管理系统,它支持事务的概念和隔离级别。事务隔离级别是指在并发环境下,数据库系统如何处理不同事务之间的相互干扰和冲突。MySQL 提供了四种事务隔离级别,分别是读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。每种隔离级别都有不同的特点和适用场景。

四种隔离级别

  • 读未提交(Read Uncommitted)隔离级别:在这个级别下,一个事务可以读取到其他事务未提交的数据。这种隔离级别的特点是脏读(Dirty Read),即读取到了其他事务未完成的数据。这种级别下并发性能最高,但数据的一致性无法保证,不推荐在生产环境中使用。
  • 读已提交(Read Committed)隔离级别:在这个级别下,一个事务只能读取到其他事务已提交的数据。这种隔离级别解决了脏读的问题,但可能会出现不可重复读(Non-Repeatable Read)的情况,即同一事务内多次读取同一数据,结果不一致。这种级别是 MySQL 的默认隔离级别。
  • 可重复读(Repeatable Read)隔离级别:在这个级别下,一个事务在执行期间能够多次读取同一数据,结果始终一致。这种隔离级别解决了不可重复读的问题,但可能会出现幻读(Phantom Read)的情况,即同一事务内多次查询,结果集不一致。MySQL 默认的事务隔离级别是可重复读。
  • 串行化(Serializable)隔离级别:在这个级别下,所有事务按顺序执行,相当于串行执行。这种隔离级别能够解决幻读的问题,但并发性能最差,一般不推荐使用。

三种存在的问题

  • 脏读(Dirty Read): 当一个事务读取了另一个正在执行但未提交的事务中的数据时,就发生了脏读。这意味着读取到的数据可能是不一致的,因为另一个事务可能在稍后被回滚。
  • 不可重复读(Non-repeatable Read): 在同一个事务中,一个查询操作在多次执行时返回的结果不一致,这主要是因为其他事务在这两次读取的过程中修改了数据。举个例子,如果你读取了一行数据,然后另一个事务修改了这行数据,然后你再次读取这行数据,你会发现数据已经改变了。
  • 幻读(Phantom Read): 当一个事务在多次查询同一范围的数据时,由于其他事务的插入或删除操作,导致每次返回的结果集不同,就会出现幻读。具体来说,你可能读取一个范围的数据,然后另一个事务插入了一些新的在这个范围内的数据,然后你再次读取这个范围的数据,会发现有一些“幻影”数据出现。

原理

读已提交底层原理

  • 更新操作:当事务要对数据进行更新操作时,会在该数据上添加写锁,防止其他事务对它进行修改,直到该事务完成。其实这不只是在读已提交级别,大部分隔离级别的查询操作都会进行锁定。
  • 读操作:在读已提交隔离级别下,读操作不会添加任何锁。当读取数据时,它会读取已经提交事务的最新版本数据。这意味着,如果一项数据在一个事务的生命周期内被另一个事务更改并提交,那么在这个事务中再次读取该数据时,会读取到新的值。
  • 多版本并发控制(MVCC):在 MySQL 的 InnoDB 存储引擎中,“读已提交”隔离级别通过 MVCC 实现。每次查询都会创建一个新的“读视图”,这个视图反映了在查询开始那一刻,所有已提交事务的更改。这样就可以防止脏读,因为总是能看到已提交的最新数据。
  • 提交:事务结束后,所有的修改将被提交到数据库中,并释放所有的锁,这些更改现在对所有新的事务可见。
    需要注意的是,在读已提交隔离级别下,由于读操作不会阻塞其他的写操作,所以可能会出现不可重复读的情况,即在同一个事务里,多次读取同一数据可能会得到不一样的结果。

但是,该级别下还是遗留了不可重复读和幻读问题: MVCC 版本的生成时机: 是每次 select 时。这就意味着,如果我们在事务 A 中执行多次的 select,在每次 select 之间有其他事务更新了我们读取的数据并提交了,那就出现了不可重复读,即:重复读时,会出现数据不一致问题,后面我们会讲解超支现象,就是这种引起的。

可重复读底层原理

可重复读的隔离级别保证了在同一个事务中多次读取同样的数据会得到相同的结果,即使在这个事务执行过程中其他事务对那些数据进行了修改或删除。

MySQL 中 InnoDB 存储引擎主要通过以下两种方式实现了可重复读:

多版本并发控制(MVCC):在这种模式下,数据库并不直接在原有的数据行上进行更新操作,而是为每个需要更新的行创建一个新版本,然后将更新操作应用到新行中。每次更新都会产生一个新版本的行,同时旧版本的行仍然保留着。依据其事务开始的时间,每个事务会看到数据的一致性快照,即便在同一个事务执行期间,其他事务对数据做了修改,也不会影响它看到的数据版本。

间隙锁(Gap Locking):间隙锁是 InnoDB 用来防止幻读的一种机制。所谓“幻读”,指的是在一个事务读取某个范围的记录行时,另一个事务在该范围插入新的记录,就可能导致在前一个事务多次读取该范围的记录时,更多的行被返回,好像从中产生了“幻影”一样。为了防止幻读的产生,可重复读隔离级别在普通的行级锁之外,增加了一个间隙锁,用于锁定一个范围,不允许其他事务在此范围内插入新行,因此可以保证同一个事务多次读取同一范围的记录时都能得到一致的结果。

总结:READ COMMITTED 级别不同的是 MVCC 版本的生成时机,即:一次事务中只在第一次 select 时生成版本,后续的查询都是在这个版本上进行,从而实现了可重复读。但是因为 MVCC 的快照只对读操作有效,对写操作无效,举例说明会更清晰一点: 事务 A 依次执行如下 3 条 sql,事务 B 在语句 1 和 2 之间,插入 10 条 age=20 的记录,事务 A 就幻读了。

1. select count(1) from user where age=20;
-- return 0: 当前没有age=202. update user set name=test where age=20;
-- Affects 10 rows: 因为事务B刚写入10条age=20的记录,而写操作是不受MVCC影响,能看到最新数据的,所以更新成功,而一旦操作成功,这些***作的数据就会对当前事务可见
3. select count(1) from user where age=20;
-- return 10: 出现幻读

MySQL 的 InnoDB 存储引擎是如何实现多版本并发控制

MVCC(多版本并发控制)是一种并发控制的方法,主要被数据库管理系统(如 PostgreSQL、MySQL 的 InnoDB 引擎)以及版本控制系统(如 Git)使用。MVCC 允许多个用户同时对同一个数据库进行读写操作,而不会互相阻塞。

MVCC 的工作原理是每当一个事务试图修改一条记录时,它不是直接去覆写这条记录,而是为这条记录创建一个印象(或称为"版本")。这个新创建的版本会携带一个时间戳,用来标记版本的创建时间。

  • 行版本化:每当需要修改表中的一行时,InnoDB 会生成行的一个新版本,旧版本的行被保留,新版本的行存入数据库中。另外,每个数据行上有两个额外的隐藏列,用于实现 MVCC 功能:一个是用于记录行的创建时间,另一个用于记录行的到期(删除)时间。这里的时间实际上是指系统版本号,系统版本号是一个随着事务产生而逐渐递增的数字。

  • 读取视图:当启动一个数据库事务时,InnoDB 会为这个事务创建一个"读取视图"。这个视图会记录下当前活动的(未提交的)事务编号。然后当我们在事务中进行 select 操作时,我们能看到的数据版本是:存活的事务版本号比我们的事务小,并且行的创建版本号要小于等于我们的版本号,到期版本号要么未定义要么大于我们的版本号。

  • Undo 日志:InnoDB 的 MVCC 要求旧版本行在事务执行过程中不能被物理删除。只能通过标记行的删除版本号实现逻辑删除,至于物理删除则由 purge 线程在稍后的时候进行。为了保留旧的行版本信息,InnoDB 维护了一种名为 undo 的日志结构。当修改数据行时,对应的旧数据版本信息添加至 undo 日志中。

通过这种设计,InnoDB 实现了 MVCC。在同一时间,读操作不会被写操作阻塞,写操作也不会被读操作阻塞,大大提高了数据库的并发处理性能。不过,维护行的多个版本会占用更多的存储空间,当数据被频繁更新时,undo 日志可能会增长很快,这是 MVCC 的一个代价。

附录

参考:https://zhuanlan.zhihu.com/p/52977862

文章来源:https://blog.csdn.net/h2453532874/article/details/134864644
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。