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

MySQL是如何实现多版本并发的?

时间:2023-03-18 22:02:35 科技观察

上一篇简单介绍了MySQL的事务隔离级别,分别是:readuncommitted、readcommitted、repeatableread、serialization。在本文中,让我们探讨一下MySQL事务隔离级别的底层原理。本文重点介绍InnoDB存储引擎的多版本并发控制。我们知道未提交读会造成脏读、幻读、不可重复读。Readingcommitted会造成幻读和不可重复读。重复阅读可能会有幻读。而连载就不会有这些问题。那么InnoDB是如何解决这些问题的呢?或者,你有没有想过造成脏读、幻读、不可重复读的根本原因是什么?这就是今天要说的主角——MVCC(Multi-VersionConcurrentControll),也就是多版本并发控制。InnoDB是一个支持多事务并发的存储引擎,它可以让数据库中的读写操作并发进行,避免因为加锁导致读阻塞。正是因为MVCC,当事务B更新id=1的数据时,事务A读取id=1的操作不会被阻塞。非阻塞的背后是不加锁的一致读。那么什么是一致阅读呢?一致的阅读很简单。查询时,InnoDB会在当前时间点创建数据库的快照。快照创建后,当前查询只能感知快照创建前提交的事务。快照创建后提交的更改、事务不会被当前查询感知到。当然,当前事务本身更新的数据是一个例外。被当前事务修改的行再次读取时可以获得最新的数据。对于其他行,仍然读取快照时的版本。而这个快照是InnoDB事务隔离级别的关键。在提交读(ReadCommitted)隔离级别下,事务中的每一次一致读都会重新生成快照。在RepeatableRead隔离级别下,事务中所有的一致性读只会使用第一次一致性读产生的快照。这就是为什么事务B提交上图中的事务后,在read-committed隔离级别下可以看到变化,而在repeatableread隔离级别下看不到变化,本质上是因为read-committed重新生成snapshot。在readcommitted和repeatableread隔离级别下,SELECT语句默认会跟随consistentread,在consistentread的场景下,不会加锁。其他修改操作也可以同步进行,大大提高了MySQL的性能。而这就是MVCC多版本并发控制的实现原理。这种阅读也叫“快照阅读”。那如果我想在交易中立即看到其他交易的提交呢?有两种方法:使用readcommitted隔离级别给SELECT加锁,共享锁和排他锁,更具体的是FORSHARE和FORUPDATE当然,第二种方法中,如果对应记录加的锁和加的锁通过SELECT是互斥的,SELECT将被阻塞。这种阅读也称为当前阅读。理解了上面的解释,下次有人问你MVCC是怎么实现的,你可以从一致性读(快照读)和当前读的角度来解释,一致性读快照使用不同的隔离级别,刷新机制也清楚解释。但我认为这还不够,我应该继续深入了解它。因为我们只知道快照,底层是怎么实现的呢?其实我们还不得而知。深度一致性读原则一般来说,不同的一致性读可能会读到不同版本的数据,所以这些必须存储在MySQL中,否则无法读取。是的,这些数据存储在InnoDB表空间中,更具体地说,这些数据存储在Undo表空间中。在InnoDB中实现MVCC的关键其实是三个字段,数据表中的每一行都有这三个字段:DB_TRX_ID这个字段有6个字节,用来存储最后插入或更新行数据的事务的唯一标识时间。你可能会问,难道只有insert和update吗?删除呢?其实在InnoDB内部,delete其实是一个update操作,只不过会更新行中的一个特定的flag,标记为deleted。DB_ROLL_PTR该字段有7个字节,可以称它为回滚指针,它指向回滚段中存储的特定UndoLog。即使当前行数据更新了,我们也可以通过回滚指针获取到更新前的历史版本数据。DB_ROW_ID该字段有6个字节。InnoDB给出了行数据的唯一标识符。这个唯一标识会在插入新数据时单调递增,就像我们平时定义表结构时定义主键时的单调递增一样。相同。DB_ROW_ID会被包含在聚簇索引中,其他非聚簇索引不会被包含。可以通过DB_ROLL_PTR获取最新的UndoLog,然后每一个对应的UndoLog指向它之前的UndoLog。这样就可以将不同的版本连接起来形成一个链表,根据需求和规则从链表中取出不同的交易。选择不同的版本读取,从而实现多版本的并发控制,像这样:有些人可能对UndoLog一无所知,记住这个:UndoLog记录的是事务开始前的数据状态,有点类似Git中的一次提交。你提交一个commit,然后开始做一个极其复杂的需求,然后做的时候心态就崩溃了。你不想要这些变化,你可以直接gitreset--hard$last_commit_id回来,最后一次提交你可以理解为UndoLog,有兴趣的可以去看看基于RedoLog和UndoLog的MySQL崩溃恢复过程一些人可能对UndoLog的组成有疑问,说UndoLog不是应该在事务提交后删除吗?为什么通过MVCC还能找到之前的数据?其实在InnoDB中,UndoLog分为两部分,分别是InsertUndoLogUpdateUndoLogForInsertUndoLog,它只会用于事务中的错误回滚,因为一旦事务被提交,InsertUndoLog是完全没用的,所以InsertUndoLog会在事务提交后删除。UpdateUndoLog则不同,它可以用于MVCC的一致性读取,为不同版本的请求提供数据源。这样一来,UpdateUndoLog是不是就无法删除了呢?因为你不知道什么时候会来一个一致性读请求,导致它占用的空间越来越大。是的,但不完全正确。一致性读本质上是指在并发处理多个事务时,需要根据需要为不同的事务提供不同的数据版本,所以如果当前不存在事务,则可以取消UpdateUndoLog。MySQL官方的建议有点骨感。建议您定期提交事务,以便定期清理机器上的UndoLogs。心想,不提交事务,整个DB就挂了,不就完了么。。EOF本文就先打住吧。至于UpdateUndoLog是怎么被kill掉的,以后有时间我会专门写一篇文章讲。聊天。