文章目录

  • 一、前言
  • 二、问题
  • 三、Mysql事务隔离级别
  • 四、MVCC
  • 五、MVCC的实现原理
  • 快照读与当前读
  • 参考


一、前言

为什么要有事务隔离级别这个概念?

因为,在数据库中,事务可以一个一个地串行,即每一个时刻只有一个事务在运行,但是当多个用户并发地存取数据库时就会产生多个事务同时存取统一数据,这种并发会带来一些问题。哪些问题呢?

二、问题

  • 丢失修改(lost update) :两个事务T1、T2同时读取、修改一份数据,T1提交破坏了T2的提交结果,导致T1的修改丢失。
  • 读“脏”数据(dirty read) :事务T1修改数据写入磁盘,T2读取结果,此时恰巧T1因为某种原因回滚撤销,那就意味着T2读取的数据是不正确的,即"脏"
  • 不可重复读(non-repeatable read) :不可重复读分为三种情况,以事务T1和事务T2为例:
  1. T1读取数据,T2更新,T1再次读取得到不同的值。
  2. T1读取数据,T2删除,T1发现数据神秘地消失了。
  3. T1读取数据,T2插入,T1发现数据无缘无故多了。

需要注意的是,很多小伙伴都听过”幻读“这个概念,事实上,不可重复读的后两种情况可称为幻影现象,也叫幻读

不同的事务隔离级别在不同情况下可以避免这些问题。

接下来我们以Mysql为例,介绍一下Mysql的事务隔离级别。

三、Mysql事务隔离级别

  • 读未提交(READ UNCOMMITTED) :⼀个事务还没提交时,它做的变更就能被别的事务看到。
  • 读提交(READ COMMITTED) :⼀个事务提交之后,它做的变更才会被其他事务看到。
  • 可重复读(REPEATABLE READ) :⼀个事务执⾏过程中看到的数据,总是跟这个事务在启动时看到的数据是⼀致的。
  • 串行化(SERIALIZABLE) :对于同⼀⾏记录,“写”会加“写锁”,“读”会加“读锁”,当出现读写锁冲突的时候,后访问的事务必须等前⼀个事务执⾏完成,才能继续执⾏。

从读未提交到串行化,可以看作隔离级别从小到大,所能解决的问题也从少到多。

Mysql默认的隔离级别是可重复读,那么如何实现读提交和可重复读呢?答案是MVCC。

四、MVCC

MVCC(Mutil-Version Concurrency Control),就是多版本并发控制。

是在数据库管理系统中,为了解决并发控制问题的一种方法。

在Mysql的Innodb引擎中,对前面介绍的Mysql中的读提交、可重复读两种事务隔离级别,当使用Select时,事务会访问版本链中的记录的过程。

这就使得别的事务可以修改这条记录,反正每次修改都会在版本链中记录。SELECT可以去版本链中拿记录,这就实现了读-写,写-读的并发执行,提升了系统的性能

五、MVCC的实现原理

每一行记录有三个隐藏键,分别为DATA_TRX_ID、DATA_ROLL_PTR、DB_ROW_ID,其中:

DATA_TRX_ID
记录最近更新这条行记录的事务 ID,大小为 6 个字节,即记住谁改我了

DATA_ROLL_PTR
表示指向该行回滚段(rollback segment)的指针,大小为 7 个字节,InnoDB 便是通过这个指针找到之前版本的数据。该行记录上所有旧版本,在 undo 中都通过链表的形式组织。

即记住原来的我说啥样的

DB_ROW_ID
行标识(隐藏单调自增 ID),大小为 6 字节,如果表没有主键,InnoDB 会自动生成一个隐藏主键,即此列。

即我的编号是啥。


下面以一个例子来展示,MVCC是怎么工作的!

假设 F1~F6 是表中字段的名字,1~6 是其对应的数据。后面三个隐含字段分别对应该行的隐含ID、事务号和回滚指针。

mysql隔离级别的实现 mysql隔离级别产生的问题_mysql隔离级别的实现


假如上面这条数据是刚 INSERT 的,可以认为隐藏自增 ID 为 1,其他两个字段则为空。当事务 1 更改该行的数据值时,会进行如下操作,如下图所示。

mysql隔离级别的实现 mysql隔离级别产生的问题_mysql_02

  1. 用排他锁锁定该行;记录 Redo log;
  2. 把该行修改前的值复制到 Undo log;
  3. 修改当前行的值,填写事务编号,使回滚指针指向 Undo log 中修改前的行。

同理,当事务2更改该行的数据值时,会进行如下操作,如下图所示。

mysql隔离级别的实现 mysql隔离级别产生的问题_数据_03

此时 Undo log 中有两行记录,并且通过回滚指针连在一起。

在 InnoDB 中存在 purge 线程,它会查询那些比现在最老的活动事务还早的 Undo log,并删除它们,从而保证 Undo log 文件不会无限增长。

如果此时事务回滚,那么可以根据回滚指针,对数据依次进行回滚并找到undo log中数据行为当前事务id的一列,进行数据恢复。

快照读与当前读

  • 快照读
    MVCC 的 SELECT 操作是快照中的数据,不需要进行加锁操作。
SELECT * FROM table ...;
  • 当前读
    MVCC 其它会对数据库进行修改的操作(INSERT、UPDATE、DELETE)需要进行加锁操作,从而读取最新的数据。可以看到 MVCC 并不是完全不用加锁,而只是避免了 SELECT 的加锁操作。
INSERT;
UPDATE;
DELETE;

在进行 SELECT 操作时,可以强制指定进行加锁操作。以下第一个语句需要加 S 锁,第二个需要加 X 锁。

SELECT * FROM table WHERE ? lock in share mode;
SELECT * FROM table WHERE ? for update;

读已提交和可重复读都是MVCC实现的,有什么区别呢?

可重复读是事务启动的时候就生成read view整个事务结束都一直使用这个read view,而在读已提交中则是每执行一条语句就重新生成最新的read view。

参考

  • 《数据库系统概论》王珊版