MySQL日志之undo log

一、undo日志的格式 

InnoDB 记录行格式:聚簇索引的记录除了会保存完整的用户数据以外,而且还会自动添加名为trx_id、roll_pointer的隐藏列,如果用户没有在表中定义主键以及UNIQUE键,还会自动添加一个名为row_id的隐藏列。所以一条记录在页面中的真实结构看起来就是这样的: 

1.1 trx_id

trx_id 列就是某个对这个聚簇索引记录做INSERT 、 DELETE 、 UPDATE 操作的语句所在的事务对应的事务id。

1.1.1 trx_id分配策略

服务器会在内存中维护一个全局变量,每当需要为某个事务分配一个 事务id 时,就会把该变量的值当作事务id 分配给该事务,并且把该变量自增1。每当这个变量的值为 256 的倍数时,就会将该变量的值刷新到系统表空间一个称之为Max TrxID的属性处。当系统下一次重新启动时,会将上边提到的 Max Trx ID 属性加载到内存中,将该值加上256之后赋值给我们前边提到的全局变量(因为在上次关机时该全局变量的值可能大于 Max Trx ID 属性值)。

这样就可以保证整个系统中分配的事务id 值是一个递增的数字。先被分配 id 的事务得到的是较小的事务id ,后被分配 id 的事务得到的是较大的事务id。

1.1.2 事务id分配时机

对于只读事务来说,只有在它第一次对某个用户创建的临时表执行增、删、改操作时才会为这个事务分配一个 事务id ,否则的话是不分配 事务id 的。

对于读写事务来说,只有在它第一次对某个表(包括用户创建的临时表)执行增、删、改操作时才会为这个事务分配一个 事务id ,否则的话也是不分配 事务id 的。

1.2、row_id

这个 row_id隐藏列赋值的方式如下: 

服务器会在内存中维护一个全局变量,每当向某个包含隐藏的row_id 列的表中插入一条记录时,就会把该变量的值当作新记录的row_id 列的值,并且把该变量自增1。 

每当这个变量的值为256的倍数时,就会将该变量的值刷新到系统表空间一个称之为Max Row ID的属性处。 

当系统启动时,会将上边提到的 Max Row ID属性加载到内存中,将该值加上256之后赋值给我们前边提到的全局变量(因为在上次关机时该全局变量的值可能大于 Max Row ID 属性值)。 

1.3 roll pointer隐藏列的含义

这个字段本质上就是一个指向记录对应的undo日志的一个指针。向undo_demo表里插入了2条记录,每条记录都有与其对应的一条 undo日志。记录被存储到了类型为 FIL_PAGE_INDEX 的数据页中,undo日志被存放到了类型为FIL_PAGE_UNDO_LOG的页面中。效果如图所示: 

二、undo日志

2.1 INSERT操作对应的undo日志

undo no 在一个事务中是从 0 开始递增的,也就是说只要事务没提交,每生成一条 undo日志 ,那么该条日 志的 undo no 就增1。 

如果记录中的主键只包含一个列,那么在类型为 TRX_UNDO_INSERT_REC 的undo日志中只需要把该列占用的存储空间大小和真实值记录下来,如果记录中的主键包含多个列,那么每个列占用的存储空间大小和对应的真实值都需要记录下来。

当我们向某个表中插入一条记录时,实际上需要向聚簇索引和所有的二级索引都插入一条记录。不过记录undo日志时,我们只需要考虑向聚簇索引插入记录时的情况就好了,因为其实聚簇索引记录和二级索引记录是一一对应的,我们在回滚插入操作时,只需要知道这条记录的主键信息,然后根据主键信息做对应的删除操作,做删除操作时就会顺带着把所有二级索引中相应的记录也删除掉。后边说到的DELETE操作和UPDATE操作对应的undo日志也都是针对聚簇索引记录而言的。

2.2 DELETE操作对应的undo日志 

使用DELETE语句把正常记录链表中的最后一条记录给删除掉,其实这个删除的过程需要经历两个阶段: 

2.2.1 阶段一

仅仅将记录的 delete_mask 标识位设置为 1 ,其他的不做修改(其实会修改记录的 trx_id 、 roll_pointer这些隐藏列的值)。InnoDB把这个阶段称之为delete mark 。 把这个过程画下来就是这样:

此时记录处于一个中间状态,其实主要是为了实现一个称之为MVCC的功能。

2.2.2 阶段二

当该删除语句所在的事务提交之后,会有专门的线程,后来真正的把记录删除掉。所谓真正的删除就是把该记录从 正常记录链表中移除,并且加入到垃圾链表 中,然后还要调整一些页面的其他信息,比如页面中的用户记录数量 PAGE_N_RECS 、上次插入记录的位置 PAGE_LAST_INSERT 、垃圾链表头节点的指PAGE_FREE 、页面中可重用的字节数量PAGE_GARBAGE 、还有页目录的一些信息等等。InnoDB 把这个阶段称之为purge 。 

把阶段二 执行完了,这条记录就算是真正的被删除掉了。这条已删除记录占用的存储空间也可以被重新利用了。画下来就是这样:

【扩展点】

每当有已删除记录被加入到垃圾链表后,都会记录该已删除记录占用的存储空间大小。之后每当新插入记录时,首先判断已删除记录占用的存储空间是否足够容纳这条新插入的记录,如果不可以容纳,就直接向页面中申请新的空间来存储这条记录。如果可以容纳,那么直接重用这条已删除记录的存储空间。

如果新插入的那条记录占用的存储空间大小小于被删除记录占用的存储空间大小,那就意味头节点对应的记录占用的存储空间里有一部分空间用不到,这部分空间就被称之为碎片空间。这些碎片空间占用的存储空间大小会被统计到PAGE_GARBAGE属性中,这些碎片空间在整个页面快使用完前并不会被重新利用,不过当页面快满时,如果再插入一条记录,此时页面中并不能分配一条完整记录的空间,这时候会首先看一看PAGE_GARBAGE的空间和剩余可利用的空间加起来是不是可以容纳下这条记录,如果可以的话,InnoDB会尝试重新组织页内的记录,重新组织的过程就是先开辟一个临时页面,把页面内的记录依次插入一遍,因为依次插入时并不会产生碎片,之后再把临时页面的内容复制到本页面,这样就可以把那些碎片空间都解放出来(很显然重新组织页面内的记录比较耗费性能)。

2.3 UPDATE操作对应的undo日志

2.3.1 不更新主键的情况 

a、就地更新(in-place update) 

更新记录时,对于被更新的每个列来说,如果更新后的列和更新前的列占用的存储空间都一样大,那么就可以进行 就地更新 ,也就是直接在原记录的基础上修改对应列的值。再先删除掉旧记录,再插入新记录。

b、先删除后插入

如果有任何一个被更新的列更新前和更新后占用的存储空间大小不一致,那么就需要先把这条旧的记录从聚簇索引页面中删除掉,然后再根据更新后列的值创建一条新的记录插入到页面中。

我们这里所说的删除并不是delete mark操作,而是真正的删除掉,也就是把这条记录从正常记录链表中移除并加入到垃圾链表中,并且修改页面中相应的统计信息(比如 PAGE_FREE 、 PAGE_GARBAGE 等这些信息)。不过这里做真正删除操作的线程并不是在唠叨 DELETE 语句中做purge操作时使用的另外专门的线程,而是由用户线程同步执行真正的删除操作,真正删除之后紧接着就要根据各个列更新后的值创建的新记录插入。

这里如果新创建的记录占用的存储空间大小不超过旧记录占用的空间,那么可以直接重用被加入到垃圾链表中的旧记录所占用的存储空间,否则的话需要在页面中新申请一段空间以供新记录使用,如果本页面内已经没有可用的空间的话,那就需要进行页面分裂操作,然后再插入新记录。

2.3.2更新主键的情况

InnoDB 在聚簇索引中分了两步处理:

将旧记录进行 delete mark 操作,这里是delete mark操作!也就是说在 UPDATE 语句所在的事务提交前,对旧记录只做一个 delete mark 操作,在事务提交后才由专门的线程做purge操作,把它加入到垃圾链表中。这里一定要和我们上边所说的在不更新记录主键值时,先真正删除旧记录,再插入新记录的方式区分开! 

根据更新后各列的值创建一条新记录,并将其插入到聚簇索引中(需重新定位插入的位置)。 

针对 UPDATE 语句更新记录主键值的这种情况,在对该记录进行 delete mark 操作前,会记录一条类型为 TRX_UNDO_DEL_MARK_REC 的 undo日志 ;之后插入新记录时,会记录一条类型为 TRX_UNDO_INSERT_REC 的 undo日志 ,也就是说每对一条记录的主键值做改动时,会记录2条 undo日志 。

在一个事务执行过程中,可能混着执行 INSERT 、 DELETE 、 UPDATE 语句,也就意味着会产生不同类型的 undo 日志 。同一个 Undo页面要么只存储 TRX_UNDO_INSERT 大类的 undo日志 ,要么只存储 TRX_UNDO_UPDATE 大类的 undo日志 ,反正不能混着存,所以在一个事务执行过程中就可能需要2个 Undo页面 的链表,一个称之为 insert undo链表 ,另一个称之为 update undo链表 ,画个示意图就是这样:

三、MVCC

3.1版本链

对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列( row_id 并不是必要的,我们创建的表中有主键或者非NULL的UNIQUE键时都不会包含 row_id 列): 

trx_id :每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的事务id 赋值给 trx_id 隐藏列。 

roll_pointer :每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到 undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。 

每次更新后,都会将旧值放到一条 undo日志中,就算是该记录的一个旧版本,随着更新次数的增多, 所有的版本都会被 roll_pointer 属性连接成一个链表,我们把这个链表称之为版本链 ,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务id 。

3.2 ReadView 

对于使用 READ UNCOMMITTED 隔离级别的事务来说,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本就好了;对于使用 SERIALIZABLE 隔离级别的事务来说,InnoDB规定使用加锁的方式来访问记录;对于使用 READ COMMITTED 和 REPEATABLE READ 隔离级别的事务来说,都必须保证读到已经提交了的事务修改过的记录,也就是说假如另一个事务已经修改了记录但是尚未提交, 是不能直接读取最新版本的记录的,核心问题就是:需要判断一下版本链中的哪个版本是当前事务可见的。为此,InnoDB提出了一个ReadView的概念,这个 ReadView 中主要包含4个比较重要的内容: 

  • m_ids :表示在生成 ReadView 时当前系统中活跃的读写事务的事务id 列表。 

  • min_trx_id :表示在生成 ReadView 时当前系统中活跃的读写事务中最小的事务id ,也就是 m_ids 中的最小值。 

  • max_trx_id :表示生成 ReadView 时系统中应该分配给下一个事务的 id 值。 

max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比方说现在有id为1,2,3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时,m_ids就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4。  

 creator_trx_id :表示生成该 ReadView 的事务的 事务id 。 

只有在对表中的记录做改动时(执行INSERT、DELETE、UPDATE这些语句时)才会为事务分配事务id,否则在一个只读事务中的事务id值都默认为0。 

有了这个 ReadView ,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见: 

  1. 如果被访问版本的 trx_id 属性值与 ReadView 中的 creator_trx_id 值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。

  2. 如果被访问版本的 trx_id 属性值小于 ReadView 中的 min_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。 

  3. 如果被访问版本的 trx_id 属性值大于 ReadView 中的 max_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。 

  4. 如果被访问版本的 trx_id 属性值在 ReadView 的 min_trx_id 和 max_trx_id 之间,那就需要判断一下trx_id 属性值是不是在 m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。 

  5. 如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。

3.3 总结

     MVCC (Multi-Version Concurrency Control ,多版本并发控制)是在使用 READ COMMITTD 、 REPEATABLE READ 这两种隔离级别的事务在执行普通的 SEELCT 操作时访问记录的版本链的过程,这样子可以使不同事务的读-写 、 写-读操作并发执行,从而提升系统性能。 READ COMMITTD 、REPEATABLE READ 这两个隔离级别的一个很大不同就是:生成ReadView的时机不同,READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadView,而REPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复使用这个ReadView就好了。

小贴士:

执行DELETE语句或者更新主键的UPDATE语句并不会立即把对应的记录完全从页面中删除,而是执行一个所谓的delete mark操作,相当于只是对记录打上了一个删除标志位,这主要就是为MVCC服务的。另外,所谓的MVCC只是在我们进行普通的SEELCT查询时才生效,

四. 崩溃恢复

在InnoDB因为某些原因停止运行后;重启InnoDB时,可能存在一个不一致的状态,这个时候我们就需要把MySQL恢复到一个一致的状态来保证数据库的可用性。这个恢复过程主要分下面这么几步:

  1.  把最新的undo log从redo log中恢复出来,因为undo log是受redo log保护的。

  2. 根据最新的undo log构建出InnoDB崩溃前的状态。

  3. 回滚那些还没有提交的事务。

经过上面这三步后,InnoDB就可以恢复到一个一致的状态,并且对外提供服务。

下面我们详细的来介绍这三部分的具体过程:

4.1 undo log的恢复

因为undo log受到redo log的保护,所以我们只需要根据最新的redo log就可以把undo log恢复到最新的状态;具体的调用过程如下:

  1. recv_recovery_from_checkpoint_start()// 从最新的一个log checkpoint开始读取redo log并应用。

  2. recv_recovery_begin() // 将redo log读取到log buffer中,并将其parse到redo hash中

  3. recv_scan_log_recs() // 扫描 log buffer中的redo log,并将redo hash中的redo log应用

  4. recv_apply_hashed_log_recs() // 应用redo log到其对应的page上。

  5. recv_apply_log_rec()->recv_recover_page()->recv_parse_or_apply_log_rec_body() 

  6. MLOG_UNDO_INSERT…

经过上述的流程之后,undo log就可以恢复到InnoDB崩溃前的最新的状态;虽然undo log已经恢复到最新的状态,但是InnoDB还没有恢复到崩溃前的最新状态;所以下一步我们就需要根据最新的undo log把InnoDB崩溃前的内存结构都恢复出来。

4.2 构建InnoDB崩溃前的状态

构建InnoDB崩溃前的状态,主要是恢复崩溃前最新事务系统的状态;通过该状态我们可以知道那些事务已经提交,那些事务还未提交,以及那些事务还未开始。

回滚段(后续补充)不管在内存中还是在文件中都是组织undo log的重要数据结构;所以我们首先需要把回滚段的内存结构恢复出来,然后根据内存中的回滚段,把活跃的事务恢复出来。其具体过程在函数trx_sys_init_at_db_start()中实现,其大致步骤如下:

  1.    通过trx_rsegs_init()扫描文件中的回滚段结构,来把rseg的内存结构恢复出来。

  2.   通过trx_rseg_mem_create()把last_page_no,last_offset,last_trx_no,last_del_marks从文件中读取上来。

  3.   然后通过trx_undo_lists_init()把rseg的四个链表:insert_undo_list,insert_undo_cached,update_undo_list, update_undo_cached从磁盘上恢复出来。

  4.    在rseg内存结构恢复好之后,我们再通过trx_lists_init_at_db_start()把活跃的事务从rseg中恢复出来。

  5.    通过trx_resurrect_insert()恢复活跃的插入类型的事务。

  6.    通过trx_resurrect_update()恢复活跃的更新类型的事务。

至此,我们就已经把InnoDB崩溃前的内存和文件状态都已经恢复出来了;其实这个时候InnoDB已经可以对外提供服务了,(毕竟内存和文件状态都就绪后我们也就可以保持一致性了);那么最后一步的事务回滚就可以交给后台线程来慢慢做事务回滚,不影响主线程对外提供服务了。

4.3 事务回滚

事务需要回滚主要有两种情况:

  1.    事务发生异常:如发生在崩溃恢复时;其活跃事务虽然被恢复出来,但是无法继续,需要将其回滚。

  2.    事务被显式回滚:如用户打开一个事务,执行完某些操作后需要将其回滚。

那么在回滚时,我们就需要借助undo log中的旧数据来把事务恢复到之前的状态;其入口函数为row_undo_step();其操作就是通过undo log来读取旧的数据记录,然后做逆向操作;主要分为下面这么几类:

  •    对于标记删除的记录清理删除标记。

  •    对于in-place更新,将数据更新为老版本。

  •    对于插入操作,删除聚集索引记录和二级索引记录。

  •    先通过row_undo_ins_remove_sec_rec()删除二级索引记录。

  •    再通过row_undo_ins_remove_clust_rec()删除聚集索引记录。

五、undo log 的清理

我们已经了解到undo log在磁盘和内存中是如何组织的;undo log是如何分配的;以及undo log是如何使用的。那么undo log会一直记录下去么?当然不是,有些undo log如果没用的话是会被回收清理的。

那么下面这将会介绍那些undo log可以清理,以及undo log是怎么进行清理的。

5.1. 几个关键的数据结构

在介绍undo log 清理之前,先介绍几个关键的数据结构;这几个数据结构对于undo log的清理实现是至关重要的。

5.1.2 trx_sys->serialisation_list

里面存放的是正在提交的事务,按照trx_t::no有序的排列;事务会在开始提交时通过 trx_serialisation_number_get() 添加至该数据结构,事务结束提交时通过trx_erase_lists()将该事务从该数据结构中移除。

5.1.3 read_view::m_low_limit_no

拥有该read_view的对象,对于trx_t::no小于read_view::m_low_limit_no的undo log都不在需要;该变量的取值时trx_sys->serialisation_list中最早的一个事务的trx_t::no;因为trx_sys->serialisation_list内有序存放的正在提交的事务,如果一个事务的trx_t::no比该数值还小,那么这个事务一定已经提交了。

5.1.4 TRX_RSEG_HISTORY与TRX_UNDO_HISTORY_NODE

这两个值共同将回滚段中的history list组织起来;在事务提交时,如果是update/delete类型的undo log,将其undo log header以头插法的方式通过trx_purge_add_update_undo_to_history()加入到该回滚段的history list中,如果是insert类型的undo log其空间会被当场释放,这是因为insert记录没有旧的版本;因此history list中的undo log header是以trx_t::no降序排列的,这里需要注意一下:history list里面的节点是undo log header。

下面我们通过一幅图来具体说明下磁盘上history list的结构。

5.2. 那些undo log可以清理

对于一个事务来说,早于read_view::m_low_limit_no的undo log都不需要访问了;那么如果存在一个read view,其read_view::m_low_limit_no比所有read view的m_low_limit_no都要小,那么小于此read_view::m_low_limit_no的undo log就不在被所有活跃事务所需要了,那么这些undo log就可以清理了。

在read_view初始化时,会使用头插法通过view_open()插入到一个全局视图链表(MVCC::m_views)中,在事务结束时通过view_close()会从全局视图链表中将此read view移除;因为是顺序插入,所以此链表中最后一个还没有close的视图就可以看做是最老的一个视图;小于此视图的undo log可以被清理,一般将此视图赋值给purge_sys::view。

现在我们已经可以决定那些undo log是可以被清理的,那么下一步我们还需要找到具体那些undo log可以清理。

在事务提交时,此事务对应的回滚段会通过trx_serialisation_number_get()加入到purge_sys::purge_queue中。purge_sys::purge_queue是一个以回滚段中第一个提交事务的trx_t::no为key的优先级队列。

如此一来,从purge_sys::purge_queue取出的回滚段中一定包含最老提交的事务,将此事务的trx_t::no与purge_sys::view对比,即可判断出此事务相关的undo log是否可以被清理。

purge_sys::purge_queue的详细信息如下图:

5.3. undo log怎么清理

解决了那些undo log可以清理的问题后,下面接着继续看undo log怎么进行清理的问题。

当放入history list的undo log且不会再被访问时,需要进行清理操作,另外数据页上面的标记删除的操作也需要清理掉,有一些purge线程负责这些操作,去入口函数为srv_do_purge() -> trx_purge(),其大致流程如下:

5.3.1获取视图

通过trx_sys->mvcc->clone_oldest_view()获取最老的视图复制给purge_sys::view,方便之后真正purge undo log时判断其是否不会再被访问到了。

5.3.2 获取purge undo log

通过trx_purge_attach_undo_recs()获取需要被purge的undo log。

5.3.3 循环获取purge undo log

通过trx_purge_fetch_next_rec()循环获取可以被purge的undo log,默认一次最多获取300个undo log record,可以通过innodb_purge_batch_size来调整。循环获取可以被purge的undo log record大致流程如下:

  1. 从purge_sys::purge_queue取出第一个回滚段,从其history list上读取最老还未被purge的事务的undo log header。

    1. 从此undo log header依次读取undo log record。

    2. 读取完毕后,重新统计此回滚段最老还未被purge的事务的位点,然后重新放入purge_sys::purge_queue;最后回到第一步。

5.3.4 undo log分发

然后将这些undo log分发给purge工作线程,purge工作线程的入口函数为row_purge_step()->row_purge()->row_purge_record()。这里purge undo log record时主要分为两种情况:清理TRX_UNDO_DEL_MARK_REC记录或者清理TRX_UNDO_UPD_EXIST_REC记录

  1. 清理TRX_UNDO_DEL_MARK_REC类型的记录,需要通过row_purge_del_mark()将所有的聚集索引与二级索引记录都清除掉。

    1. 清理TRX_UNDO_UPD_EXIST_REC类型的记录,需要通过row_purge_upd_exist_or_extern()将旧的二级索引清理掉。

5.3.5 undo log清理

通过trx_purge_truncate()来对history list进行清理,其大致流程如下:
遍历所有回滚段,并通过trx_purge_truncate_rseg_history()对回滚段中的history list进行清理,其大致流程如下:

  1. 将history list最后一个事务的undo log header读取出来。

    1. 判断此undo log是否已经被purge,如果已经被purge则继续;如果没有被purge则退出。

    2. 将此事务所有的undo log释放,并从history list上删除;会到第一步。

5.3.6  undo tablespace空间重建

在这之后,如果发现某些undo tablespace空间占用过大,被标记需要通过trx_purge_truncate_marked_undo()进行对其truncate,其大致流程如下:

  1. 创建一个undo_trunc.log的标记文件,来表明当前undo tablespace正在进行truncate;这是为了保证在truncate中间发生重启时可以顺利重建此undo tablespace。

    1. 通过trx_undo_truncate_tablespace()接口来对其文件做真正的truncate。

    2. 删除undo_trunc.log标记文件,表明undo tablespace的truncate已经完成。

注意:当一个undo tablespace被标记为需要truncate时,不会再有事务从此undo tablespace分配回滚段,而且进行truncate时必须保证该undo tablespace上所有的undo log都已经被purge。

六、undo log的应用

undo log的应用主要有以下两方面:

6.1 事务回滚,崩溃恢复

此功能主要满足了事务的原子性,简单的说就是要么做完,要么不做。因为数据库在任何时候都可能发生宕机;包括停电,软硬件bug等。

数据库就需要保证不管发生任何情况,在重启数据库时都能恢复到一个一致性的状态;这个一致性的状态是指此时所有事务要么处于提交,要么处于未开始的状态,不应该有事务处于执行了一半的状态;所以我们可以通过undo log在数据库重启时把正在提交的事务完成提交,活跃的事务回滚,这样就保证了事务的原子性,以此来让数据库恢复到一个一致性的状态。

6.2 多版本并发控制(MVCC)

此功能主要满足了事务的隔离性,简单的说就是不同活跃事务的数据互相可能是不可见的。因为如果两个活跃的事务彼此可见,那么一个事务将会看到另一个事务正在修改的数据,这样会发生数据错乱;所以我们可以借助undo log记录的历史版本数据,来恢复出对于一个事务可见的数据,来满足其读取数据的请求。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值