MySql学习笔记–事务的隔离级别
文章目录
一、事务的特性
原子性:事务中的操作要么全都执行要么全都不执行。
一致性:数据的完整性。数据库中A有100,B有200。他俩无论怎么转账,都要保证A和B共有300。
隔离性:数据库中多个事务对数据进行修改时,保证每个事务在使用数据时对其他事务是隔离的。(每个事务都有一个完整的数据空间)
持久性:事务结束后,对数据的修改是永久的(系统故障也不会丢失)。
InnoDB引擎通过redolog(重做日志)-------------------------保证------------------------》持久性
undolog(回滚日志)-------------------------保证------------------------》原子性
MVCC --------------------------保证------------------------》隔离性
持久性+原子性+隔离性------------------------保证------------------------》一致性
二、并行事务引发的问题
脏读----事务A读到了别的事务没提交的数据。
不可重复读----事务A对记录X前后读取的数据不一致(两次读取之间有事务B对X修改了)
幻读----事务A查询符合条件f的记录数量前后不一致(两次查询之间有事务B插入了符合f条件的记录)
与上面对应的MySql四种隔离级别来规避这些现象:
这里给出一个不同隔离级别能解决的问题:
反正锤子(读未提交)是一点用没有,要想解决脏读就得用刀(读提交),要想解决不可重复读就得用宝剑(可重复读),要想解决幻读就得掏出真理(串行化)。但是这些天赋一个比一个贵啊(性能开销大),一般不使用真理,MySql一般使用宝剑(可重复读)就能很大程度避免幻读,但不是完全。这个下一节笔记再说。。。。。
三、四种隔离级别具体的实现
1.读未提交(锤子)和串行化(真理)
读未提交什么问题都无法解决,只是在读取最新的数据而已,执行命令了就去机械的读。
串行化通过加读写锁的方式避免并行访问。
2.读已提交(刀)和可重复读(宝剑)
这两个都是通过MVCC来完成。区别就在于创建MVCC中的快照ReadView的时机不同。读已提交是在执行每个sql语句前都要生成一个新的快照;而可重复读是在启动事务时生成一个快照,并在这个事务执行期间一直就用这一个。
注意:开始事务和启动事务不是一回事
3.MVCC以及ReadView
先说下MVCC:
MVCC(多版本并发控制),解决多线程并发问题,基于undolog、版本链和ReadView实现。在InnoDB中一条记录的字段包括两个隐藏列
trx_id:修改这条记录的事务id;
roll_pointer:指向旧版本的记录,记录都存到undo日志中,通过这个指针找到之前版本的该行记录。
这个就是一条记录的改动日志了。日志中记录了不同版本这条记录的样子以及该版本出自的事务。
我们再来介绍ReadView:
ReadView的作用就是让事务知道应该读取的是哪个版本的记录。
活跃的读写事务指的是还没提交的事务。举个例子来说明下这个字段的意思:
假设现在处在MySql的默认隔离级别(可重复读级别下)现在只有一个事务A,对记录X要进行访问。那么A先生成对X的ReadView,且A事务没结束之前一直用这个ReadView、X现在的记录字段:
m_ids:生成这个ReadView时,只有事务A是活跃的(没提交)。
min_trx_id:m_ids中最小值就是51.
max_trx_id:若待会还有别的事务要对X访问,那么他的id应为52.
creator_trx_id:生成该ReadView的事务是51.
以上就是字段的解读,
而具体ReadView如何判断哪个版本是对事务可见的需要对比字段:
解释:trx_id:就表示创建该版本记录时的事务id
trx_id == creator_trx_id :我读取我创建的记录;
trx_id < min_trx_id :创建该版本记录的事务不在活跃事务中(已提交了),那在读已提交(刀)和可重复读(剑)下就能访问该版本的记录。
trx_id > max_trx_id :创建该版本记录的事务是在未来的事务中(也即我来晚了。。。),所以不能访问该版本的记录。
min_trx_id <= trx_id <= max_trx_id:
(1)如果trx_id在m_ids中,说明创建该版本记录的事务还活跃,没提交,不可访问。
(2)如果trx_id不在m_ids中,说明创建该版本记录的事务已提交,可访问。
接下来继续用这个例子说明可重复读的工作机制。
四、那么可重复读下是如何工作的呢
现在紧接着另一个事务B也启动了(上面的还是那个状态)。
m_ids:生成view2时,事务A和事务B都没提交;
max_rex_id:再有事务C的话他的id应为53;


ReadView判断是否可见的具体分析:
事务B读取时,trx_id = 50。进行判断:trx_id < min_trx_id,则可访问。读到余额100万。
事务A要修改,trx_id = 50,先进行判断:trx_id < min_trx_id,则可访问,修改成了200万,但还没提交。但会把这个记录串到undolog中:

事务B再读时,trx_id 变成了51。进行判断:min_trx_id <= trx_id <= max_trx_id,且trx_id 还在 m_ids中,则说明还没提交,不能访问这个版本的记录,根据roll_pointer指针找旧版本的记录,读到余额还是100万。
事务A提交了,由于是在可重复读的隔离级别下,事务B使用的视图始终是View2。
事务B再次读取,由于视图没变,所以最后还是min_trx_id <= trx_id <= max_trx_id,且trx_id 还在 m_ids中,则说明还没提交,不能访问这个版本的记录,根据roll_pointer指针找旧版本的记录,读到余额还是100万。
五、那么读已提交下是如何工作的呢
读提交时,最大的区别就是,每次读取数据都创建新的视图。还是上边的例子:
m_ids:生成view2时,事务A和事务B都没提交;
max_rex_id:再有事务C的话他的id应为53;


事务B第二次读取时,创建的视图始终是View2这样子的,在A事务提交完之后,B再查询时,视图变成了View3。



总结
以上就是今天总结的学习笔记,感谢两位up主优质的文章,本文中的大部分图都来自于这两篇文章中,感谢。。
https://xiaolincoding.com/mysql/transaction/mvcc.html;
https://blog.youkuaiyun.com/lans_g/article/details/124232192。