一看就会的MySQL事务与MVCC-全栈之路

144 阅读6分钟

前言

什么是事务?

事务是数据库区别于文件系统的重要特性之一,引入事务的主要目的他会把数据库从一种一致状态转化为另一种一致的状态。

MVCC

MVCC是innoDB的多版本并发控制技术,要搞懂 MVCC,最好是要先懂 InnoDB 中事务及事务的隔离级别,不然单纯看概念很难弄明白 MVCC。

MySQL InnoDB事务的特性

有四大特性

就是ACID

分别是

原子性(Atomicity)

当前事务的操作要么同时成功、要么同时失败。原子性由undo log日志来保证,因为undolog入职记载着数据修改之前的信息。

例如 我们插入一条数据, 那么undo log日志会记录一条对应的日志,我们要update那条记录时,undo log 就会记录update之前的数据, 如果事务执行出现异常,那么会回滚 , InnoDB就是利用unodo log记录的数据,来讲数据恢复到事务开始之前的。

一致性(Consistency)

一致性由业务逻辑保证

隔离性(Isolation)

在事务并发执行时,他们内部的操作是不能互相干扰的, 如果多个事务可以同时操作同一个数据,那么就会产生脏读、重复读、幻读的问题。

持久性(Durability)

一旦提交了事务,他对数据库的改变就应该是永久的,说白了就是,会将数据持久化在硬盘上。

而持久性由redo log 日志来保证,当我们要修改数据时,MySQL是先把这条记录所在的「页」找到,然后把该页加载到内存中,将对应记录进行修改。

MySQL InnoDB事务隔离级别

MySQL中的事务隔离级别一共分为4个

  • ru 未提交读(READ UNCOMMITTED)
  • rc 提交读 (READ COMMITTED)
  • rr 可重复读 (REPEATABLE READ)
  • 可串形化(SERIALIZABLE)

1、READ UNCOMMITTED

READ UNCOMMITTED 提供了事务之间最小限度的隔离。 除了容易产生虚幻的读操作喝不能重复的读操作之外,处于这个隔离级别还可以读到其他事务还没有提交的数据(脏读), 如果这个事务查询了其他事务不提交的变化作为计算基础, 将产生脏数据。

2、READ COMMITTED

READ COMMITTED 隔离级别的安全性比 REPEATABLE READ 隔离级别的安全性要差。 处于READ COMMITTED 级别的事务可以看到其他事务对数据的修改, 就是说在事务处理期间,如果其他事务修改了相应的表,那么同一个的多个Select可能返回不能的结果(幻读)

3、REPEATABLE READ

解决了幻读

事务中的同一个select将返回同样的结果,在事务期间。

4、SERIALIZABLE

如果隔离级别为序列化,则用户之间通过一个接一个顺序地执行当前的事务,这种隔离级别提供了事务之间最大限度的隔离。

MVCC

多版本并发控制

在MVCC下,就可以做到读写不阻塞,且避免了类似脏读这样的问题。那MVCC是怎么做的呢?

MVCC通过生成数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取。

回到事务隔离级别下,针对于 read commit (读已提交) 隔离级别,它生成的就是语句级快照,而针对于repeatable read (可重复读),它生成的就是事务级的快照。

read commit如何利用mvcc解决脏读?(当前读)

前面提到过read uncommit隔离级别下会产生脏读,而read commit (读已提交) 隔离级别解决了脏读。思想其实很简单:在读取的时候生成一个”版本号”,等到其他事务commit了之后,才会读取最新已commit的”版本号”数据。

比如说:事务A读取了记录(生成版本号),事务B修改了记录(此时加了写锁),事务A再读取的时候,是依据最新的版本号来读取的(当事务B执行commit了之后,会生成一个新的版本号),如果事务B还没有commit,那事务A读取的还是之前版本号的数据。

通过「版本」的概念,这样就解决了脏读的问题,而「版本」其实就是对应快照的数据。

repeatable read (可重复复读)隔离级别是怎么避免不可重复读的问题?(快照读)

快照读

repeatable read (可重复复读)隔离级别是「事务级别」的快照!每次读取的都是「当前事务的版本」,即使当前数据被其他事务修改了(commit),也只会读取当前事务版本的数据。


而repeatable read (可重复复读)隔离级别会存在幻读的问题,「幻读」指的是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。

在InnoDB引擎下的的repeatable read (可重复复读)隔离级别下,快照读MVCC影响下,已经解决了幻读的问题(因为它是读历史版本的数据)

而如果是当前读(指的是 select * from table for update),则需要配合间隙锁来解决幻读的问题。

MVCC原理

undo log前面也提到了,它会记录修改数据之前的信息,事务中的原子性就是通过undo log来实现的。所以,有undo log可以帮我们找到「版本」的数据

而read view 实际上就是在查询时,InnoDB会生成一个read view,read view 有几个重要的字段,分别是:trx_ids(尚未提交commit的事务版本号集合),low_limit_id(下一次要生成的事务ID值),low_limit_id(尚未提交版本号的事务ID最小值)以及creator_trx_id(当前的事务版本号)

在每行数据有两列隐藏的字段,分别是DB_TRX_ID(记录着当前ID)以及DB_ROLL_PTR(指向上一个版本数据在undo log 里的位置指针)

铺垫到这了,很容易就发现,MVCC其实就是靠「比对版本」来实现读写不阻塞,而版本的数据存在于undo log中。

而针对于不同的隔离级别(read commit和repeatable read),无非就是read commit隔离级别下,每次都获取一个新的read view,repeatable read隔离级别则每次事务只获取一个read view

本文总结:

  • 事务为了保证数据的最终一致性
  • 事务有四大特性,分别是原子性、一致性、隔离性、持久性
    • 原子性由undo log保证
    • 持久性由redo log 保证
    • 隔离性由数据库隔离级别供我们选择,分别有read uncommit,read commit,repeatable read,serializable
    • 一致性是事务的目的,一致性由应用程序来保证
  • 事务并发会存在各种问题,分别有脏读、重复读、幻读问题。上面的不同隔离级别可以解决掉由于并发事务所造成的问题,而隔离级别实际上就是由MySQL锁来实现的
  • 频繁加锁会导致数据库性能低下,引入了MVCC多版本控制来实现读写不阻塞,提高数据库性能
  • MVCC原理即通过read view 以及undo log来实现

相关文章及参考

juejin.cn/post/704404…

juejin.cn/post/701648…

segmentfault.com/a/119000001…