当前位置: 首页 > 科技观察

MySQL底层MVCC、回滚段、一致性读、锁读的日常学习

时间:2023-03-15 16:06:32 科技观察

MySQL数据库中的事务你已经了解了吗?看下图,从1到6依次执行,按照RR的顺序执行在隔离级别下,事务A和事务B输出的num值分别是多少?我们预先创建这样一张表,并初始化一条数据:CREATETABLE`test1`(`id`int(11)NOTNULLAUTO_INCREMENTCOMMENT'主键Id',`num`int(11)NULLCOMMENT'数量',PRIMARYKEY(`id`))ENGINE=InnoDB;insertintotest1(id,num)values(1,1);然后按照上图的顺序开始执行各个事务,这就需要我们打开3个操作窗口分别执行三个事务A、B、C:事务A:事务A事务B:事务B事务C:事务C执行按照上图的执行顺序commit,事务C自动commit。我们需要展示的commit,事务A和B的输出结果如下:TransactionA:num=1TransactionB:num=3为什么会这样输出呢?背后是:MVCC(多版本并发控制)、一致性读(一致性永久读等MySQL数据库的基础知识)、锁定读(lockingreading)等。1.MVCCMySQL数据库官网文档在此描述了MVCC方式:官网链接:https://dev.mysql.com/doc/refman/8.0/en/innodb-multi-versioning.html淘宝的数据库内核月报提到To:多版本控制:指的是一种改进的技术并发。在最早的数据库系统中,只有读和读可以并发,读和写、写和读、写和写必须是阻塞的。引入多个版本后,只有写入和写入块互为阻塞,其他三个操作可以并行化,大大提高了InnoDB的并发性。在内部实现上,与Postgres在数据行上实现多个版本不同,InnoDB是在undolog中实现的,通过undolog可以获取数据的历史版本。可以将检索到的数据的历史版本提供给用户读取(根据隔离级别的定义,部分读取请求只能看到较旧的数据版本),也可以在回滚时覆盖数据页上的数据。InnoDB内部记录了一个全局的活跃读写事务数组,主要用于判断事务的可见性。目前MVCC的实现依赖于:隐藏字段(DB_TRX_ID,DB_ROLL_PTR)回滚日志(undolog)一致读(consistentread)你也可以这样理解MVCC:当一个事务更新数据时,先更新旧的把旧的数据放在一个单独的地方(回滚段),其他事务读取数据时,根据DB_TRX_ID和DB_ROLL_PTR计算undolog链中数据的当前版本。2.Consistentread(一致读)继续看官方文档中consistentread的描述:官网链接:https://dev.mysql.com/doc/refman/8.0/en/glossary.html#glos_consistent_read直译:读取操作使用基于时刻的快照信息显示查询结果,而不管其他并发运行的事务执行的更改。如果查询到的数据被其他事务更改,则根据undolog中的内容重建原始数据。这种技术通过强制事务等待其他事务完成来避免一些降低并发性的锁定问题。在RR层面,在执行第一个读操作时会创建一个一致的读视图ReadView,事务后续的读都是基于这个视图的数据;在RC层面,每次读操作都会创建一个最新的ReadView,所以每次selectread都可以获得当前提交事务的最新数据;“consistentread”是InnoDB引擎在RC和RR隔离级别下处理select语句的默认模式。因为“一致读”不需要在它访问的表上设置任何锁,所以其他会话在对表执行“一致读”时可以自由修改这些表。另外:读未提交(readuncommitted)和可序列化(serializable)不需要依赖MVCC。Readuncommitted每次可以直接读取当前数据的最新值。而serializable就是直接使用加锁操作,让所有的事务串行执行,牺牲了并发能力。一致性读的实现:在每个事务开始的那一刻,会构造一个数组(m_??ids)来记录当前所有“活跃事务”(事务开始,但还未提交)的ID;数组中最小的交易ID为低水位;数组中最大交易ID+1为高水位;数据版本可见性规则:当前数据某个版本是否可见取决于当前数据的DB_TRX_ID与一致视图数组中记录的事务ID的比较判断:低水位前的数据版本可见,高水位后的数据版本是不可见的。在低水位和高水位之间,需要检查数组中是否存在当前数据版本的DB_TRX_ID。如果存在,说明事务还没有提交,不可见。如果不存在则表示事务已提交且可见。按照一致读的理解,事务B创建了自己的快照数据,它的输出应该是num=2,为什么num=3呢?但是如果不是num=3,那么提交的事务C操作不就丢失了吗?(更新丢失的问题)这里再介绍一个知识点:更新数据是先读后写,而这次读只能读到当前值,称为“当前读”(currentread)。3.Currentreads也称为lockingreads。官方文档:https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.htmlInnoDB引擎支持两种方法Lockreads以提供额外的安全性(MySQL5.7版本):#readlock(S锁,共享锁)SELECT...LOCKINSHAREMODE;#writelock(Xlock,exclusivelock)SELECT...FORUPDATE;lockread会在读取的数据上加一个共享锁,其他事务可以读取记录,但不能修改记录,直到当前事务提交。锁读验证:为什么需要锁读?如果先查询一个事务中的一条数据,然后插入或更新相关数据,这时一个事务B同时过来更新或删除你要查询的记录,就会出现幻读问题。这就是为什么MVCC不能完全解决幻读问题,而是需要MVCC+行锁+间隙锁(next-keylock)的方法。4、事务A、B、C的执行过程继续看开头的第一张图:starttransactionwithconsistentsnapshot;这条SQL语句可以立即启动事务并创建当前事务的一致读取快照。效果相当于启动事务,然后立即执行select语句。我们先看一下文章开头的三个事务修改数据行的过程,按照步骤1到步骤6如下:如果仔细查看上图中三个事务的穿插执行过程,可以发现A、B、C的三个事务不管是commit还是rollback,最终都能得到正确的数据。这就是InnoDB引擎下多版本并发控制(MVCC)的实现原理。总结一下关键点:每个事务都会创建一个数据快照,创建快照的时机根据隔离级别不同;每笔交易都会生成一个全球唯一的DB_TRX_ID来标记当前版本;DB_ROLL_PTR是回滚指针的意思,结合DB_TRX_ID最终确定我要获取的数据;DB_TRX_ID、DB_ROLL_PTR、undolog这三个值控制数据的版本;update和delete操作都是先读后写,这个读是锁定读(当前读)。