MySQL事物隔离级别

举一个转账的情景,一个人A给另外一个人B转账,这个时候A转账之后数据库的数据被修改了,但是这个时候断电了,就会出现钱凭空消失的情况。因此可以在转账操作前先开启事务,等所有数据库操作执行完成之后才提交事务,该事物会对数据库的修改将永久生效,如果中途发生中断或错误,事物期间对数据库所做的修改将会被回滚到没执行该事务之前的状态。

事务的特性

1.原子性
一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态。
2.一致性
事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态。比如A有800,B有600,A往B转账200,不会出现A有600,B还是只有600的情况
3.隔离性
数据库允许多个并发事务同时对其数据进行读写和修改的能力。多个事务同时使用相同的数据时,不会相互干扰,每个事务都有一个完整的数据空间,对其他并发事务是隔离的。
4.持久性
事务处理结束后,对数据的修改是永久的,即便系统故障也不会丢失。

持久性:通过redo log(重做日志)来保证的
原子性:通过undo log(回滚日志)来保证的
隔离性:通过MVCC或锁机制来保证的
一致性:持久性+原子性+隔离性来保证的

由BEGIN;开始
且有COMMIT;结束

并行事务会引发什么问题?

MySQL服务端允许多个客户端连接,MySQL会出现同时处理多个事务的情况,会出现脏读、不可重复读、幻读的问题。

脏读

如果一个事务【读到】了另一个【未提交事务修改过的数据】,就意味着发生了【脏读】的现象。

不可重复读

在一个事务内多次读取同一个数据,如果出现前后两次读到的数据不一样的情况,就意味着发生了【不可重复读】的现象。

幻读

在一个事务内多次查询某个符合查询条件的【记录数量】,如果出现前后两次查询到的记录数量不一样的情况,就意味着发生了【幻读】现象。
严重性:脏读>不可重复读>幻读

事务的隔离级别有哪些?

读未提交:一个事物还没提交时,它做的改变就能被其他事务看得到(可能发生脏读、不可重复读和幻读现象)
读提交:一个事务提交之后,它做的变更才能被其他事务看到(可能发生不可重复读和幻读现象,不可能发生脏读现象)
可重复读:一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB引擎默认隔离级别(可能发生幻读现象,不可能发生脏读和不可重复读现象)
串行化:会对记录加上读写锁,在多个事务对这条记录读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。(啥都不可能发生)
在串行化的时候,比如说你进行了一个SELECT语句,那么此时其实是相当于上了一个读锁,也就是在SELECT语句后面加上了 LOCK IN SHARE MODE;

这几个隔离级别的顺序是从上到下级别依次提高,隔离级别越高,性能效率就越低。

MySQL InnoDB引擎的默认隔离级别虽然是【可重复读】,但是他可以很大程度上避免幻读现象,但并不是完全解决了,解决的方案有两种:
1.对于【快照读】(普通SELECT语句)而言,这由MVCC实现,实现的方式是开始事务后(执行begin语句后),在执行第一个查询语句后,会创建一个Read View,后续的查询语句利用这个Read View在undo log版本链接到事务开始时的数据,所以事务过程中每次查询的数据都一样,即使中途有其他事务插入了新纪录,是查询不出来这条数据的。

2.当前读比如update,insert,delete这些语句执行前都会查询最新版本的数据,然后再做进一步的操作。假设要去update一个记录,但是另外一个事务已经delete这条记录并已经提交事务了,这样会产生冲突,因此一定要读取到最新的数据。
InnoDB引擎为了解决【可重复读】隔离级别使用【当前读】而造成的幻读问题,引出了间隙锁。
事实上他会有一个next-key lock,这样的话另一个事务在执行插入语句的时候,判断到插入的位置被事务加了next-key lock的话,就会生成一个**【插入意向锁】,同时【进入等待状态】**,直到那个事务提交了。

幻读并没有被完全解决!

MVCC并不可以完全避免幻读现象的发生!
场景1:
事务A begin;
事务A SELECT * FROM t_id WHERE id = 5; 这个时候由于t_id没有这一行所以是empty
在这之后事务B开始整活儿
事务B begin
事务B INSERT INTO t_id VALUES(5,‘小美’,18);
事务A UPDATE t_id SET ·name· = ‘xiaolincoding’ WHERE id = 5;
这样看起来很违和,因为明明不是说他只能看到事务开始前的数据吗?现在居然可以做更新!而且你做完更新之后还能SELECT!
SELECT * from ·t_id· WHERE id = 5;
这个时候还能索引到!
场景2:
事务A先执行快照读:SELECT * FROM ·t_id· WHERE ·id· > 100;这个时候得到了3条数据
事务B插入一个id=200的记录并提交
事务A执行当前读语句,SELECT * FROM ·t_id· WHERE id > 100 FOR UPDATE;这个时候会得到4条记录,也发生了幻读的现象

ReadView

我们刚才提到了在可重复读这个隔离级别的时候对于快照读,通过MVCC实现,事务开始后,在执行第一个语句后,会创建一个ReadView。
首先,ReadView里面有四个重要的字段

  • m_ids:创建ReadView时,当前数据库中【活跃事务】的事务id列表,活跃任务说的是启动了但还没有提交的事务
  • min_trx_id:创建ReadView时,当前数据库中【活跃事务】中id最小的事务,也就是m_idx中的最小值
  • max_trx_id:创建ReadView时,当前数据库中应该给下一个事务的id值
  • creator_trx_id:创建该Read View的事务的id

对于使用InnoDB存储引擎的数据库表而言,他的聚簇索引都包含了下面两个隐藏列

  • trx_id:当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务id记录在这个列里面

  • roll_pointer:每次对某条聚簇索引进行改动的时候,都会把旧版本的记录写入到undo日志中,这个隐藏列是一个指针,指向每一个旧版本记录,可以通过它找到修改前的记录

  • 如果记录的trx_id值小于ReadView中的min_trx_id的值,表示这个版本的记录在创建ReadView之前已经提交的事务中生成的,所以该版本的记录对当前事务可见。

  • 如果记录的trx_id值大于等于ReadView中的max_trx_id的值,表示这个版本的记录是在创建Read View后才启动的事务生成的,该版本的记录对当前事务不可见

  • 如果记录的trx_id值在Read View的min_trx_id和max_trx_id之间,需要判断trx_id是否在m_idx列表中①如果记录的trx_id在m_ids列表中,表示生成该版本记录的活跃时间依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见

  • 如果记录的trx_id不在m_ids列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见

所以这种通过【版本链】来控制并发事务访问同一个记录时的行为就交MVCC(多版本并发控制)

可重复读是如何工作的?

可重复读隔离级别是启动事务的时候生成一个Read View,然后整个事务期间都在用这个Read View。
下面这个例子十分透彻!
事务 A 和 事务 B 的 Read View 具体内容如下:
在事务 A 的 Read View 中,它的事务 id 是 51,由于它是第一个启动的事务,所以此时活跃事务的事务 id 列表就只有 51,活跃事务的事务 id 列表中最小的事务 id 是事务 A 本身,下一个事务 id 则是 52。
在事务 B 的 Read View 中,它的事务 id 是 52,由于事务 A 是活跃的,所以此时活跃事务的事务 id 列表是 51 和 52,活跃的事务 id 中最小的事务 id 是事务 A,下一个事务 id 应该是 53。
接着,在可重复读隔离级别下,事务 A 和事务 B 按顺序执行了以下操作:

  • 事务 B 读取小林的账户余额记录,读到余额是 100 万;
  • 事务 A 将小林的账户余额记录修改成 200 万,并没有提交事务;
  • 事务 B 读取小林的账户余额记录,读到余额还是 100 万;
  • 事务 A 提交事务;
  • 事务 B 读取小林的账户余额记录,读到余额依然还是 100 万;

事务 B 第一次读小林的账户余额记录,在找到记录后,它会先看这条记录的 trx_id,此时发现 trx_id 为 50,比事务 B 的 Read View 中的 min_trx_id 值(51)还小,这意味着修改这条记录的事务早就在事务 B 启动前提交过了,所以该版本的记录对事务 B 可见的,也就是事务 B 可以获取到这条记录。
事务 A 通过 update 语句将这条记录修改了(还未提交事务),将小林的余额改成 200 万,这时 MySQL 会记录相应的 undo log,并以链表的方式串联起来,形成版本链,由于事务 A 修改了该记录,以前的记录就变成旧版本记录了,于是最新记录和旧版本记录通过链表的方式串起来,而且最新记录的 trx_id 是事务 A 的事务 id(trx_id = 51)。

事务 B 第二次去读取该记录,发现这条记录的 trx_id 值为 51,在事务 B 的 Read View 的 min_trx_id 和 max_trx_id 之间,则需要判断 trx_id 值是否在 m_ids 范围内,判断的结果是在的,那么说明这条记录是被还未提交的事务修改的,这时事务 B 并不会读取这个版本的记录。而是沿着 undo log 链条往下找旧版本的记录,直到找到 trx_id 「小于」事务 B 的 Read View 中的 min_trx_id 值的第一条记录,所以事务 B 能读取到的是 trx_id 为 50 的记录,也就是小林余额是 100 万的这条记录。

当事物 A 提交事务后,由于隔离级别时「可重复读」,所以事务 B 再次读取记录时,还是基于启动事务时创建的 Read View 来判断当前版本的记录是否可见。所以,即使事物 A 将小林余额修改为 200 万并提交了事务, 事务 B 第三次读取记录时,读到的记录都是小林余额是 100 万的这条记录。

读提交

读提交和可重复读的区别在于读提交这个隔离级别每次读取数据的时候,都会生成一个新得Read View。
事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。
还用刚才的例子去分析!

查询操作方法需要使用事务吗?

需要看隔离级别决定。如果,不加事务的话,假如说有很多个SELECT语句,我要做一个报表,这个时候没有办法保证时间点是一致的。
高并发用RC,如果要经常做报表用RR。

Buffer Pool 是啥?

1.当读取数据时,如果数据存在于 Buffer Pool 中,客户端就会直接读取 Buffer Pool 中的数据,否则再去磁盘中读取。
2.当修改数据时,首先是修改 Buffer Pool 中数据所在的页,然后将其页设置为脏页,最后由后台线程将脏页写入到磁盘。(后台线程是随机进行刷盘的)
Buffer Pool是在MySQL启动的时候,向操作系统申请的一片连续的内存空间,默认配置下有128MB。
Buffer Pool里面除了缓存【索引页】和【数据页】,还包括了undo页,锁信息等。InnoDB为每一个缓存页都创建了一个控制块,控制块信息包括【缓存页页号、缓存页地址】等等。
在这里插入图片描述
所以,这个Buffer Pool里面在某一时刻可能既存在空闲的缓存页,又存在使用的缓存页。在这种情况下, 如果一个个去遍历是十分耗时的。因此,创建了一个链表,在这个链表当中,空闲缓存页的【控制块】作为了链表的节点,也就是会有一个Free链表。为了记录链表的状态,这个链表中还会有一个头节点,这个头节点中包括【起始地址】【尾结点地址】【空闲的缓存页】信息。
在这里插入图片描述
除了Free链表之外,还会有一个Flush链表,只不过这个链表当中存放的都是脏页的缓存页控制块。
何时刷盘?
其实进行随机刷盘的,为了防止MySQL宕机,先写日志,再写入磁盘,通过redo log日志让MySQL拥有处理宕机的能力。

  • 当redo日志满了的情况下,主动触发脏页刷盘
  • MySQL认为空闲的时候,后台线程会定期适量地将脏页刷盘
  • MySQL正常关闭之前,将所有脏页进行刷盘

为了提高缓存命中率,可以考虑使用LRU算法来实现管理。
1.当访问的页在buffer pool里,就直接把该页对应的LRU链表节点移动到链表的头部
2.当访问的页不在buffer pool里,除了要把页放入到LRU链表的头部,还要淘汰LRU链表末尾的节点

但是简单的LRU算法会导致【预读失效】和【Buffer Pool污染】的问题
预读失效
MySQL在加载数据页的时候,会把相邻的页一并加载进来,但是这些加载进来的相邻的页可能没有被访问从而失效了。
放在LRU上来讲,相邻的页可能放在链表的头部,他可能没有被访问但是尾部删除的页有可能是频繁访问的页,这样就导致预读失效。
为了解决这个问题,LRU算法划分两个区域,old区域和young区域。
在这里插入图片描述
Buffer Pool污染
由于BufferPool大小是有限的,可能会出现大量热数据被淘汰,等下次这些热数据再次被访问的时候有需要重新进行磁盘I/O。
这种情况下,我们要提高进入young区域,也就是提高进入热点区域的门槛。具体而言,只有同时满足【被访问】与【在old区域停留时间超过1秒】两个条件,才会被插入到young区域头部。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值