15. 3 InnoDB的多版本

InnoDB是一个支持多版本的存储引擎。它保留着被修改记录的旧版本数据,用以一些事务特色,比如并发和回滚。这些旧版本数据被存储在undo表空间, 使用的数据结构是回滚段(rollback segment)。请查看 章节 15.6.3.4, “Undo Tablespaces” 了解更多信息。在事务回滚时,InnoDB使用回滚段 中的信息来进行undo操作。InnoDB也使用历史数据来构建某条记录的历史版本来支持一致性读。请查看 章节 15.7.2.3, “Consistent Nonlocking Reads”.

InnoDB为每条记录添加三个隐藏字段;

  • DB_TRX_ID字段,事务id,6字节。用来标识最后修改或插入该条记录的事务id。删除操作也被当做更新操作对待,只是有一个特殊位来标识该记录已被删除。
  • DB_ROLL_PTR字段,回滚指针,7字节。指向回滚段中的一条undo log记录。如果记录被更新,undo log记录中含有用来重构更新前的记录的必要信息。
  • DB_ROW_ID字段,row ID,6字节。在插入信息记录时,row ID会自动增加。如果InnoDB自动生成了一个聚簇索引,索引记录中会包含row ID字段,否则, DB_ROW_ID不会出现在索引中。

回滚段中的undo logs被分为insert和update两类。insert类型的undo log只用于事务回滚,在事务提交后就可以立即删除。update类型的undo log也被 用于一致性读,但是只有在所有使用该undo log的事务都提交后,才能删除,因为在一致性读中,需要这些undo log来构建数据记录的早期版本。想了解关于 undo log的更多信息,请查看章节 15.6.6 ,“Undo Logs”。

建议及时提交事务,包括哪些只包含一致性读的事务。否则,InnoDB就不能删除相关的undo log,这会导致回滚段变得很大,最后占满所在表空间。想了解更多关于 管理undo表空间的信息,请查看章节 15.6.3.4 “Undo Tablespaces”。

回滚段中的一条undo log记录所占的存储空间大小通常比插入或更新的记录小。你可以根据这个特点来估算回滚段的大小。

在InnoDB的多版本机制中,当你使用sql语句删除一条记录时,并不会立即进行物理删除。只要在删除相关的update类型的undo log时,InnoDB才会对对应记录和相关的索引 记录进行物理删除。这个删除操作叫做purge,耗时很短,通常和删除操作用的时间差不多。

如果你以相似的速率同时进行小批量插入和删除,purge线程就可能开始落后,相关表会因为这些标记删除的记录变的越来越大,使磁盘空间紧张,所有操作变慢。这种情形, 需要限制新的行记录操作,为purge分配更多的资源(通过系统变量innodb_max_purge_lag)。想了解更多信息,请查阅章节 15.8.9 “Purge Configuration”。

多版本和二级索引 #

mvcc处理聚簇索引和二级索引的方法不同。聚簇索引中的记录被原地更新,且会有隐藏字段指向相关的undo log记录。而不同的是,二级索引记录中没有隐藏的系统字段,也 没有被原地更新。

当一条二级索引记录被更新时,旧的二级索引记录被标记删除,新的记录被插入,被标记删除的记录最终会被purge。当一条二级索引记录被标记删除,或二级索引页被 一个新的事务更新时,InnoDB查找聚簇索引中的记录。在聚簇索引中,如果在读取记录的事务初始化后,相关记录被修改,InnoDB会检查记录的DB_TRX_ID,并根据undo log来生成记录的正确版本。

如果一条二级索引记录被标记删除,或者二级索引页被新的事务更新,不会发生覆盖索引。InnoDB会查找聚簇索引中的记录,而不是返回二级索引中的记录。

然而,如果我们开启了索引下推,且where条件的部分字段用到了二级索引,MySQL server仍然会将部分where条件下推到存储引擎。如果没有匹配的记录,就避免了 查找聚簇索引。如果有匹配的记录,即使是被标记删除,InnoDB也会回表查询聚簇索引。