mvcc 事务版本控制
1.多个事务共同操作一条数据可能产生的问题
多个事务同时对一条数据进行crud 操作可能遇到的问题呢 这里其实 可能会遇到 脏写丶脏读、不可重复读、幻读的情况
1.脏写
脏写 就是 假设现在有两条事务 此时 a事务将值更新为a的值 a会生成一条 undo log b更新为b的值 b也生成一条undo log 在这个时候 a的线程这个时候回滚了
在a更新之前这个值是空的这个时候 a突然回滚了 此时 a根据undo log 将那条值改成了 null ,a就将 b的值一并回滚成了null 此时 事务b查询时 这个值就变成了null 这个就是脏写
2.脏读
接下来继续脏读的问题 还是同样两个事务 此时 a事务更新了某行数据 但是事务未提交 此时事务b来读取 读取到了事务 a的值 这个时候事务b还在很high的操作数据 此时a事务突然回滚了事务 这个时候b事务再去读取a修改的值 发现竟然是空这个就是脏读
总结: 无论是脏读 还是脏写 都是 某个事务去去读取或者是修改了另外一个还没提交的事务更新或者是修改的数据,因为事务还没提交,他随时可能反悔,就会导致你的数据没了或者是之前查到的数据没了这就是脏读或者是脏写
3.不可重复读
我们假设 现在有一个事务A开启了 并且这个时候事务b 去更新数据 在事务b没提交之前 事务A是读取不到事务b的数据的 这样就可以避免脏读和脏写问提 但是我们这个时候 a事务开启了这个时候a事务多次对一条数据进行读取 但是这个时候 事务b更改后提交了事务 这个时候事务a读取的值就发生了变化 因为事务b提交了事务 这个时候a就读取不到前面的值了 如果a是想在这个事务期间读取到的是还没变化的值这个时候就读取不到了 ,这个问题就叫不可重复读
4.幻读
幻读到底是个什么东西 幻读就是假设一个事务a 开启了 这个时候 他查询 select * from xx where id>1 这个时候 查询出来了 10条数据 但是这个时候 事务b开了 这个时候事务b 网数据库插入了两条数据 这个时候事务a再去查询 发现 woc 我是不是出现了幻觉 刚刚都还是10条数据 这个时候突然出现了 12条数据 这个情况就是幻读
这些问题都是并发事务问题,所以数据库设计了事务隔离机制 ,mvcc多版本隔离机制 ,锁机制
2.数据库的隔离机制
1.sql标准的4种隔离机制
read uncommitted(读未提交),read committed(读已提交),repeatable read(可重复读),serializable(串行化) 不同的隔离级别可以解决不同的问题
| 隔离级别 | **脏写 ** | 脏读 | 幻读 | 幻读 |
|---|---|---|---|---|
| read uncommitted | false | true | true | true |
| read committed | false | false | true | true |
| repeatable read | false | false | false | true |
| serializable | false | false | false | false |
这个是标准的事务隔离级别 一般是不会用串行化的 因为串行化就不允许 事务串行了 但是Mysql的隔离级别 RR级别已经可以避免幻读了 为什么sql可以做到RR级就可以防止幻读这个是主要是依托 mysql 的mvcc机制 有了这个机制才能将 RR级别事务隔离解决幻读的问题
3.mvcc机制前奏 undo log版本链
简单来说呢,我们每条数据其实都有两个隐藏字段,一个是trx_id,一个是roll_pointer,这个trx_id就是最近一次更新这条数据的事务id,roll_pointer就是指向你了你更新这个事务之前生成的undo log,关于undo log之前都讲过了,这里不用多说了。
举个例子
假设现在有一个事务a id=50 ,插入了一条数据 此时隐藏的字段指向的 undo log 是空的 插入的值是a rool_pointer 指向的是一个空的 undolog 所以之前是没有值的。
此时如果友来了一个事务b 这个时候 他把值改成了b 这个时候会生成一个新的 undo log 记录之前的值 ,然后会让roll_pointer指向实际回滚的这个值 !
此时 又来了一个事务c 这个时候 事务c 就会指向刚刚修改的事务b形成一个版本链条
这就是这个多个事务串行更新一行数据的时候,txr_id和roll_pinter两个隐藏字段的概念,包括undo log串联起来的多版本链条的概念!
4.ReadView ReadView 到底是个什么东东
ReadView,简单来说,就是你执行一个事务的时候,就给你生成一个ReadView,里面比较关键的东西有4个
1.一个是m_ids,这个就是说此时有哪些事务在MySQL里执行还没提交的;
2.一个是 min_txr_id 这个就是m_ids里最小的值
3.一个是max_txr_id 这个了就是下个要生成的事务id 就是最大id
4.一个是 creator_txr_id 这个就是你当前事务的id
1.假设原来数据库有一行数据 很早之前就插入过了 事务id是32 这个是他的初始值 这个时候 事务 a(id=45 )一个是事务b(id =59)去修改他的值
这个时候a去查询这个数据 现在事务a 会开启一个 ReadView 里面包含了 事务id 45 和 60两个 id 这个时候事务a 去查询这行数据 去走一个判断 判断这行数据的事务id txr_id 是否小于ReadView 中的最小id 这个时候一查询 发现 txr_id =32 小于ReadView中的id 说明 这个数据是在事务开启前提交的 所以此时查询的数据就是id为32的这条数据
- 如果此时 事务b来修改了这个值并且提交 这个时候事务a 查询的时候值变成了事务 b的id59 这个时候发现是大于min_txr_id 最小值 和小于max_txr_id 最大值的说明是差不多时间开启的 这个时候 就去m_ids 去看一下是不是存在 然后发现事务id59存在于 m_ids 里面这个时候 就去查之前的undo log 然后查询到之前的值
- 如果此时 事务a自己更新了这个值 让事务id 变成了45同事保存 事务b的快照 当a开来查询的时候发现事务id为45 这个时候是可以读取的 因为是自己修改的值
-
此时 在事务a执行的过程中突然开启了事务c 这个事务的id为78 然后他更新了那个值为c 然后提交了事务
说明 a事务开启后有一个事务更新了事务 是自己看不到的 此时就会顺着undo log多版本链条往下找,自然先找到值A自己之前修改的过的那个版本,因为那个trx_id=45跟自己的ReadView里的creator_trx_id是一样的,所以此时直接读取自己之前修改的那个版本,如下图。
ReadView 他能够保证在你只能读到事务开启你事务开启前别的事务提交的值 时候开启后的值你是读不到的或者是你开启后比你晚开启的值你也是读不到的
5.RC级别是如何根据readView 实现的
首先假设我们的数据库里有一行数据,是事务id=50的一个事务之前就插入进去的,然后现在呢,活跃着两个事务,一个是事务A(id=60),一个是事务B(id=70),此时如下图所示。
然后事务b 进行了update 操作 然后数据的事务id变成70 生成一条undo log 有roll_pointer指向
这个时候事务a来查询 发现这条数据的事务id是70 也就是说,属于ReadView的事务id范围之间,说明是他生成ReadView之前就有这个活跃的事务,是这个事务修改了这条数据的值,但是此时这个事务B还没提交,所以ReadView的m_ids活跃事务列表里,是有[60, 70]两个id的,所以此时根据ReadView的机制,此时事务A是无法查到事务B修改的值B的。
然后这个时候 事务b提交了 这个时候事务a再来查询 事务a会生成一个新的ReadView 由于事务 b提交了这个时候事务 b 不在m_ids里面 这个时候事务a读取到的值就是事务b id=70的值了
这就是 Rc +readView 的实现
6.RR级别是如何根据readView 实现的
首先假设我们的数据库里有一行数据,是事务id=50的一个事务之前就插入进去的,然后现在呢,活跃着两个事务,一个是事务A(id=60),一个是事务B(id=70),此时如下图所示。
然后事务b 进行了update 操作 然后数据的事务id变成70 生成一条undo log 有roll_pointer指向
这个时候事务a来查询 发现这条数据的事务id是70 也就是说,属于ReadView的事务id范围之间,说明是他生成ReadView之前就有这个活跃的事务,是这个事务修改了这条数据的值,但是此时这个事务B还没提交,所以ReadView的m_ids活跃事务列表里,是有[60, 70]两个id的,所以此时根据ReadView的机制,此时事务A是无法查到事务B修改的值B的。
然后这个时候 事务b提交了 这个时候事务a再来查询 RR级别 ReadView 一旦生成就不会变了 在开始的时候 是有60 和70两个值 所以当事务a再次查询的时候事务里面还是只有两个事务 所以他会顺着 undolog 链 继续往下找 不会读取到事务b的值 这就是RR级别 undolog 版本链+ReadView 的实现