数据库-03-MVCC及日志

1,093

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情

MVCC及日志

上一节讲到事务,其中可重复读和读提交的底层实现其实就是MVCC,本节将围绕MVCC以及底层的log去进行分析,其中log分为undo,redo和binlog。

MVCC

MVCC是Multi-Version Concurrency Control的缩写,也就是多版本并发控制。MVCC的实现分为两个部分,第一部分是Read View的四个字段。

截屏2023-01-04 18.01.18.png

第二部分是数据隐藏的两个字段。

截屏2023-01-05 22.24.35.png

要判断一个数据对于当前事务是否可见,就是根据以上两个部分进行判断。

根据Read View可以得出所有的事务被分成了三种。

截屏2023-01-05 22.24.47.png

只有两种情况数据对事务是可见的。

第一个是创建该条数据的事务为已提交事务,也就是trx_id<min_trx_id。

第二个是创建该条数据的事务大于min_trx_id,并且小于max_trx_id,而且不在m_ids内,也就是创建该Read View时在活跃范围内但是已经提交的事务。

其它的情况都是不可见的,那么会通过roll_pointer到undo log中寻找符合条件的数据。

RC和RR都是通过MVCC去实现的,RC是每一次读数据时都会创建Read View,而RR则是整个事务只会创建一个Read View。

日志

在MySql数据库中有三种比较常用且重要的日志,分别是undo log,redo log和binlog。

undo log

undo log是用来记录旧版本的日志,它的作用有两个,分别是回滚和辅助MVCC。

首先第一个作用,回滚。

在我们修改一条数据前,MySql就会把当前数据记录在undo log中,如果事务发生崩溃,那么就回滚到旧数据中。而且不同的操作记录的数据不同。

如果是插入数据,那么只需要记录主键值,回滚时只需要删除对应的主键记录。

如果是删除数据,那么需要记录所有的数据,回滚时要根据数据对数据库进行插入操作。

如果是更新数据,那么只需要记录更新列的旧值,回滚时更新为旧值即可。

其次第二个作用,辅助MVCC。

从上一章可以看到MVCC依靠Read View和数据的隐藏字段,其中的一个隐藏字段roll_pointer,这个指针就是指向undo log。

截屏2023-01-06 21.50.12.png

Read View根据内部信息在undo log版本链中找到最新的可见数据。

Buffer Pool

在讲redo log前,需要先讲一下Buffer Pool,因为redo log的更新是在Buffer Pool更新之后。

Buffer Pool顾名思义是Innodb引擎的缓存池,在读取数据时,如果缓存池内有对应的数据则直接读取缓存池数据,否则的话则从数据库中读取。如果是更新数据,如果数据存在于缓存池中,则在缓存池内修改,修改完毕后该数据所在页会被标记为脏页,将脏页加入到flush列表中,后台进程加载flush列表中的脏页到磁盘中。

由此可见,Buffer Pool并不是存储的具体数据行,而是数据所在页,在申请一片连续的空间后会按照设定的数据库页大小(默认为16KB)将该空间分成多个页,会有一个free列表用来记录哪一个页是空闲的,如果没有空闲页的话则通过LRU淘汰数据。

Buffer Pool除了会存储索引页和数据页,还会缓存undo页和锁信息等。

redo log

Buffer Pool位于内存中,总归是不可靠的,因此在改变Buffer Pool数据后需要记录redo log用来表示在哪个表中的哪个数据页的xxx偏移量上发生了什么更改。也就是在真正数据落盘之前需要先通过日志进行记录,在合适的时机再进行刷盘,这就是WAL(Write Ahead Log)技术。在系统崩溃后,可以通过redo log将数据恢复到最新状态。

那么redo log和数据持久化同样都是写入磁盘,那么redo log是不是多此一举呢?

其实不是的,redo log更新是在日志后追加数据,属于顺序写,而数据持久化需要找到更新数据的位置,属于随机写,顺序写的速度要比随机写高得多。

redo log由两个重做日志组成,由于redo log是为了解决Buffer Pool的脏页丢失问题,因此不需要去记录全量的数据,只有脏页数据为有效数据,由此innodb引擎选择了循环写的方法。

截屏2023-01-07 15.03.16.png

如图蓝色部分为为待刷新的脏页数据,红色部分为用来记录新数据的部分。如果write pos追上了check pointer,那么MySql会发生阻塞,此时会停下来等待Buffer Pool刷新脏页到磁盘中,接着对redo log进行擦除,腾出旧空间后check pointer往后移,Mysql恢复正常。

binlog

前两个日志都是innodb引擎生成的,而binlog则是不论什么引擎都有的,因为它在数据库的server层生成数据。在数据库执行一条更新数据后,会在binlog中写入,当事务提交了会将所有的binlog写入到binlog文件中。

binlog和redo log不同,它不是循环写,而是全量地记录,如果一个binlog文件写满了,那么将创建一个新的binlog文件。binlog文件有三种写入模式,分别是STATEMENT(默认)、ROW、 MIXED

STATEMENT:记录的是具体的SQL语句,但是可能会有一些动态函数会导致主从不一致。

ROW:记录的是修改后的数据,不会出现动态函数问题,但是会导致binlog文件过大,比如update语句如果是ROW模式则会更新多少记录就会产生多少记录,而STATEMENT则只会记录一条update语句。

MIXED:它会根据不同的情况使用STATEMENT和ROW。

由于binlog记录的是全量数据,因此可以用于备份数据和主从同步。MySql的主从复制就是依赖binlog,在主库写完binlog提交数据后,将该binlog复制发送到从库中,从库接收到后回放binlog文件,更新数据。

总结

本节分析了MVCC的实现,以及三种常见的数据库日志,分别是undo log,redo log和binlog,但是只是简单分析了其作用和写入方式,后续会对数据库日志有更加深入的分析,敬请期待。

感谢观看!