做烘焙网站,如何做好企业推广,电商网站设计方案大全,做网站企业经营范围文章目录 前言一. 预备知识二. 模拟MVCC三. Read View四. RC与RR的本质区别结束语 前言 MVCC#xff08;多版本并发控制#xff09;是一种用来解决读-写冲突的无锁并发控制 MVCC为事务分配单向增长的事务ID#xff0c;为每个修改保存一个版本#xff0c;版本与事物ID相关联… 文章目录 前言一. 预备知识二. 模拟MVCC三. Read View四. RC与RR的本质区别结束语 前言 MVCC多版本并发控制是一种用来解决读-写冲突的无锁并发控制 MVCC为事务分配单向增长的事务ID为每个修改保存一个版本版本与事物ID相关联读操作只读该事务开始前的数据库的快照所以MVCC可以解决以下问题
在并发读写数据库数据时读操作不用阻塞写操作写操作也不用阻塞读操作提高了数据库并发读写性能同时解决脏读幻读不可重复读等事务隔离性问题但不能解决更新丢失问题
数据库并发的场景
数据库并发场景有三种
读-读不存在任何问题不需要并发控制但有使用共享锁读-写有线程安全问题可能会造成事务隔离性问题如脏读幻读不可重复读写-写有线程安全问题可能会存在更新丢失问题如第一类丢失更新问题和第二类更新丢失问题
补充
第一类更新丢失问题回滚丢失Lost update
第一类更新丢失是指一个事务被撤销可能导致其他事务已提交的更新数据被覆盖
时间序号事务一事务二T1begin开启事务begin开启事务T2查询余额money1000查询余额money1000T3存款100money1100T4取款100money900T5commit提交事务T6回滚取款操作money恢复1000 正如上述事例事务一二开始查询余额都是1000事务二先进行存款操作并提交。 事务一不知道事务二的存在进行取款操作但是又进行了回滚就会将余额恢复成最开始查询的数额这就覆盖了事务二的更新操作 第二类更新丢失问题覆盖丢失/两次更新问题Second lost update
第二类更新丢失是指当两个事物或多个事务查询相同的记录然后各自基于查询结果更新数据
时间序号事务一事务二T1begin开启事务begin开启事务T2查询余额money1000查询余额money1000T3取款100money900T4commit提交事务T5存款100money1100T6commit提交事务 事务一二查询相同余额1000事务二先进行取款操作money900但事务一后续基于自己的查询结果进行存款操作money1100这就覆盖了事务二的数据更新 一. 预备知识
学习MVCC前我们要有以下三个知识了解
3个记录隐藏字段undo日志undo logRead View
3个记录隐藏字段
这3个字段是记录信息
DB_TRX_ID6byte。最近修改该记录的事务ID记录创建这条记录/最后一次修改改记录的事务IDDB_ROLL_PTR7byte。回滚指针指向这条记录的上一个版本指向历史版本历史版本在undo log中DB_ROW_ID6byte。隐含的自增ID隐藏主键如果数据表没有主键InnoDB会自动以DB_ROW_ID产生一个聚簇索引
补充实际还有一个标记删除/更新的flag字段在事务中删除记录会将该flag字段标记为删除
比如如下学生表有name和age两个属性
mysql select * from student;
-------------
| name | age |
-------------
| 张三 | 28 |
-------------但其实还有3个隐藏字段
nameageDB_TRX_IDDB_ROW_IDDB_ROLL_PTR张三28最后修改该记录的事务ID隐式主键 1回滚指针(指向历史记录) undo log
MySQL是网络进程服务所有的索引事务隔离性日志等都是在内存中完成的即在MySQL内部的相关缓冲区中保存数据再在合适的时候进行刷盘将数据写入磁盘达到持久化 所以undo log简单理解就是MySQL中的一段内存缓冲区用来保存日志数据 在数据库事务开始之前MySQL会将记录保存在undo log中如果事务回滚或者数据库崩溃时可以利用undo log日志中记录的日志信息进行回退。同时也可以提供多版本并发控制下的读MVCC undo log的生命周期 undo log产生在事务开始之前生成 undo log销毁当事务提交之后undo log并不能马上删除而是放入待清理的链表由purge线程判断是否有其他事务在使用undo log保存的上一个事务之前的版本信息决定是否可以清理undo log的日志空间 注意undo log也会产生redo logundo log也需要持久化保护 undo log和redo log的区别 undo log是逻辑日志实现事务的原子性 undo log记录的是事务开始前的数据状态记录的是更新前的值undo log实现事务的原子性提供回滚 redo log是物理日志实现事务的持久性 redo log记录的是事务完成后的数据状态记录的是更新后的值redo log实现事务的持久性保证数据的完整性 Read view稍后再讲解因为需要快照这一概念
二. 模拟MVCC
假设现在有一个事务其事务ID为10对student表中记录进行修改update将name张三改成name李四
MVCC过程如下
因为是修改所以要给该记录加行锁修改前先将原本的数据拷贝一份到undo log中相当于undo log中有一个备份写时拷贝然后MySQL相当于有两行相同的记录修改是修改原始记录的name并且修改原始记录的隐藏字段DB_TRX_ID为修改该数据的事务ID即10而该记录的回滚指针DB_ROLL_PTR里面写入undo log中副本数据的地址表示上一个版本事务10提交释放锁
结果如下图 此时最新的记录就是name李四’的那条
接着又有一个事务11要对student表进行修改update将age(28)改成age(38)
因为是修改所以需要给该记录加行锁修改前拷贝一份原始数据到undo log中将原始数据的age(28)改成age(38)并且修改DB_TRX_ID为事务11IDDB_ROLL_PTR指向undo log中的备份数据地址表示上一个版本数据
结果如下图 如此就形成了一个基于链表记录的历史版本链。回滚其实就是利用历史数据覆盖当前数据 上述的一个个版本被称为一个个快照
update可以形成版本delete和insert同样也可以。 delete删除数据是设置flag字段为删除回滚只要再修改flag字段即可 insert插入数据虽然没有历史版本但是为了回滚操作insert的数据也会被放入undo log中如果当前事务commit提交了那么undo log的历史insert记录就会被清空 有了undo logselect读取就被分为了两种读 快照读读取历史版本当前读读取最新数据select lock in share mode(共享锁)select for update。增删改也是读取当前数据 当有多个事务同时增删改时都是当前读势必需要加锁此时select如果也是当前读那就会被阻塞这就是串行化 但如果是快照读读取历史版本则不受加锁限制可以并发运行这就是MVCC的意义。
隔离级别决定了select是当前读还是快照读 事务总是有先有后而事务可以分为三个阶段执行前执行中执行后 隔离性的目的就是让不同的事务看到它该看到的内容
三. Read View
如何实现隔离级别呢其实就是实现了Read View
Read View是事务进行快照读操作时产生的一个读视图在该事务执行的快照读的那一刻会生成数据库系统当前的一个快照记录并维护系统当前活跃事务的ID每个事务开始时都会被分配到一个ID此ID是自增的事务越新ID值越大
Read View在MySQL源码中是一个类。本质是用来进行可见性判断的。即当我们某个事务执行快照读时对该记录创建一个Read View读视图以此判断当前事务能够看到哪个版本的数据既可能是当前最新数据也可能是该记录在undo log里的某个历史版本数据
比较关键的属性如下
class ReadView
{
...
private:
trx_id_t m_low_limit_id;
trx_id_t m_up_limit_id;
trx_id_t m_creator_trx_id;
ids_t m_ids;
bool m_closed;
...
}m_ids创建视图时的活跃事务id列表m_low_limit_id翻译为高水位生成ReadView时系统尚未分配的下一个事务ID也就是目前已有的事务ID的最大值1大于等于这个ID的事务均不可见m_up_limit_id翻译为低水位记录m_ids列表中事务ID的最小ID小于这个ID的事务均可见m_creator_trx_id创建该读视图的事务ID
我们在实际读取数据版本链的时候能读取到每一个版本对应的事务ID也就是隐藏字段DB_TRX_ID 而通过DB_TRX_ID和以上四个属性作比较就可以判断该记录是否应该被读取到 m_ids列表记录着形成快照的时活跃的事务ID
如果记录中的DB_TRX_ID和m_up_limit_id即m_ids中最小的事务ID作比较小于这个事务ID代表该事务一定已经提交其记录一定是历史数据可以读取如果记录中的DB_TRX_ID等于m_creator_trx_id代表是自己修改的数据可以读取如果记录中的DB_TRX_ID在m_ids中代表修改该记录的事务还未提交或在形成快照后才提交不可读取如果记录中的DB_TRX_ID大于等于m_low_limit_id即在快照形成时系统还未分配的事务ID代表该数据是在快照形成后才形成的不可读取
如果查找不应该看的版本可以按照回滚指针跳转到上一个历史版本直到符合条件 模拟Read View过程
假设当前有记录;
nameageDB_TRX_IDDB_ROW_IDDB_ROLL_PTR张三28null1null
目前不关心创建该记录的事务ID并且因为是创建的记录所以没有历史版本所以回滚指针为null
事务操作
事务1[id1]事务2[id2]事务3[id3]事务4[id4]beginbeginbeginbegin………修改且提交进行中快照读进行中………
事务4修改name(张三)变成name(李四)
当事务2对某行数据进行快照读时数据库会为该行数据生成一个Read View读视图 事务2的Read View m_ids1,3 up_limit_id1 low_limit_id415读视图生成时系统尚未分配的下一个事务ID creator_trx_id2 此时的版本链如下 因为事务4在事务2形成快照前就提交了所以是可见的
事务2在快照读时就会拿该记录的DB_TRX_ID跟Read View中的几个属性比较判断该版本是否可见 比较步骤 DB_TRX_ID4 up_limit_id1? 不小于下一步 DB_TRX_ID4 low_limit_id(5) ? 不大于下一步 m_ids.contains(DB_TRX_ID) ? 不包含说明事务4不在当前的活跃事务中。 四. RC与RR的本质区别
RC即Read Committed读提交RR即Repeatable Read可重复读 详细定义可见【MySQL】事务
Read View生成时机的不同从而造成RCRR级别下快照读的结果不同
RR级别的快照读 在RR级别下的某个事务对某条记录的第一次快照读会创建一个快照和Read View将当前系统活跃的其他事务记录起来 之后再快照读时还是使用同一个Read View所以只要当前事务在其他事务提交之前使用过快照读那么之后的快照读使用的都是同一个Read View对之后的修改不可见 即在RR界别下快照读生成Read View时Read View会记录此时所有其他活动事务的快照这些事务的修改对于当前事务都是不可见的而早于Read View创建的事务所做的修改均是可见的
RC级别的快照读 在RC级别下事务没词快照读都会新生成一个快照和Read View所以即使后来的事务提交了其修改结果也可见因为RC级别下的Read View是每次快照读都会新形成的 RC级别下的Read View是每次快照读都会新形成而RR级别的Read View只会在第一次快照读时形成
推荐文章 【MySQL笔记】正确的理解MySQL的MVCC及实现原理 详细分析MySQL事务日志redo log和undo log 【MySQL】InnoDB 如何避免脏读和不可重复读
结束语
感谢看到此处 如果觉得本篇文章对你有所帮助的话不妨点个赞支持一下博主拜托啦这对我真的很重要。