事务隔离实现原理

记录一下事务隔离性是怎么实现的呢?

一、事务的隔离级别

1.Read Uncommitted(未提交读):一个事务可以读取到其他事务未提交的数据,会出现脏读,所以叫做 RU,它没有解决任何的问题。

2.Read Committed(已提交读),也就是一个事务只能读取到其他事务已提交的数据,不能读取到其他事务未提交的数据,它解决了脏读的问题,但是会出现不可重复读的问题。

3.Repeatable Read (可重复读),它解决了不可重复读的问题,也就是在同一个事务里面多次读取同样的数据结果是一样的,但是在这个级别下,没有定义解决幻读的问题。

4.Serializable(串行化) ,在这个隔离级别里面,所有的事务都是串行执行的,也就是对数据的操作需要排队,已经不存在事务的并发操作了,所以它解决了所有的问题

Mysql的默认隔离级别是Repeatable read(重复读)。

二、实现方式

有两大类的方案 ,LBCC与MVCC。

Lock Based Concurrency Control(LBCC):要保证前后两次读取数据一致,那么读取数据的时候,锁定要操作的数据,不允许其他的事务修改。而大多数应用都是读多写少的,这样会极大地影响操作数据的效率

MVCC:一种多版本并发控制机制,基于数据的多版本去进行控制,无锁化的设计方案。

三、MVCC具体怎么实现隔离性的呢?

其实是A事务开启第一条语句的时候,就分配使用了一个版本的快照版本号,后面多次查询都是用的那个快照,别的事务修改了干扰不到A。

但要注意的是,如果A事务里两次查询之间有update(或update和delete),那么A里面就会加载新的快照,就不是上面那个效果了,是为了维护数据精准性。MVCC是为了提高并发效率,而这里更新到最新是为了维持精确。

一句话读各自快照,更新数据的话大家互相更新下。insert、update和delete会更新版本号

四、MVCC本身怎么实现的呢?

1 多个版本的行数据

InnoDB中记录数据的基本单位为页(InnoDB Page,默认16KB),页的类型有有多种的,比如**存储当前数据的数据页(B-Tree Node)、存储逻辑回滚/备份数据的undo 页(Undo Log Page)**等。

当执行insert/update/delete写操作时,除了要修改对应数据页之外,还会对之前的数据进行备份(记录至undo页中)。如果事务需要回滚,找到对应的undo 记录进行应用回滚即可。

注意:哪怕事务尚未提交,写操作也会立即修改当前的数据页。所以回滚要到undo log中找。

多出的事务id字段

行数据是会有多个版本的(当前数据页 + undo页),为了区分各个版本的数据,每一行记录都会额外多出一个隐藏的版本号字段(trx_id),trx_id即对应创建此记录时事务id。(底层只有创建啥的)

每个事务都能分配到一个全局递增的事务id(trx_id),当该事务进行写操作时,会将该值一并写入行记录中

多出的roll_pointer字段

还会多出roll_pointer字段,用来存修改之前那个条内容的地址

因为每次修改都会把老版本写入undo日志中,这个roll_pointer就是存了一个上个版本指针,它指向这条聚簇索引记录的上一个版本的位置,这样就形成了一个版本链

示例:

当前事务id=20,更新:[set name=lisi where id = 1]

  1. 找到对应记录的所在记录页;
  2. 修改记录为:[id=1, name=lisi, trx_id=20]
    同时生成undo log:[log_type="update", id=1, name=zhangsan, trx_id=10]

回滚时:找到undo log进行应用,反向更新数据回 [id=1, name=zhangsan, trx_id=10]

2.事务快照

如果我们给数据库中的事务拍一张照片的话,我们会看到:在拍照的那一瞬间,有的事务已经提交,有的正在运行中,有的事务尚未开始

在这里插入图片描述

事务快照,黑色表示事务已提交

当隔离级别为【可重复读】时:已经提交的则看得见(图中黑色),还没提交的自然就看不见,否则就是脏读了。

在【可重复读】隔离级别下,一旦触发快照后,这个快照会一直存在,直至事务结束。哪些事务已提交,哪些没提交,也会在这一瞬间定格。这也就保证了我们永远都在同一张照片里面“找”数据,从而保证了【可重复读】。

【可重复读】隔离级别下,事务快照的触发时机主要有:

  • 开启事务后(begin/start transaction;),执行第一条常规读SQL(select)时;
  • 开启事务时,直接开启快照:start transaction with consistent snapshot.

原理总结

总的来说就是根据当前的事务id,和read-view(当前已经提交的最大事物id(max_id)和未提交事务里最小id(min_id))。然后去版本链里比对,找出符合可重复读的记录。

五、幻读问题

MVCC有的情况下可以解决,但是有的情况下解决不了

1.可以解决的情况

mysql里面实际上有两种读,一种是“快照读”,比如我们使用*select进行查询,就是快照读,在快照读的情况下是可以解决幻读的问题的。使用的就是MVCC

多数数据库都实现了多版本并发控制,并且都是靠保存数据快照来实现的。

事务每次取数据的时候都会取创建版本小于当前事务版本的数据,以及过期版本大于当前版本的数据。

原理:将历史数据存一份快照,所以其他事务增加与删除数据,对于当前事务来说是不可见的。

2.MVCC不可以解决的情况

另外一种读是:“当前读”。对于会对数据修改的操作(update、insert、delete)都是采用当前读的模式,当前读,都会读取最新的快照数据(修改操作会触发去读最新快照

3.next-key 锁解决幻读

next-key 锁包含两部分

  1. 记录锁(行锁)

  2. 间隙锁

记录锁是加在索引上的锁,间隙锁是加在索引之间的。

原理:将当前数据行与上一条数据和下一条数据之间的间隙锁定,保证此范围内读取的数据是一致的。

比如update加范围,锁住这个范围

间隙锁在Session_1下面执行update account set name =‘zhuge’ where id > 10 and id <=20;,则其他Session没法在这个范围所包含的间隙里插入或修改任何数据 。不过没索引的话,会升级成表锁。

原文地址:https://www.cnblogs.com/chz-blogs/p/14255322.html