InnoDB 是一个数据多版本的存储引擎,它会保持它修改的数据的旧版本数据以此来支持事务特性,比如并发操作和事务的回滚。这些旧版本数据存储在一个叫做rollback segment的数据结构中(回滚段),当事务回滚的时候,Innodb会使用回滚段的数据来执行事务的撤销操作,也会使用这些老版本的数据来做旧版本的一致性读操作(可重复读的隔离级别下需要用到)。
在Innodb内部会增加三个字段存储相关信息,6个字节的 DB_TRX_ID 字段标识插入或者更新该字段的最后一个事务的事务ID,删除操作在内部是视为更新操作,在行上标记为已删除,每一行包含一个7个字节的DB_ROLL_PTR字段,也叫着行指针,行指针指向undo log在回滚段(rollback segment)中的位置, 如果该行被更新了,undo log 包含了该行被更新前的必要的重建行的信息。6个字节的 DB_ROW_ID 字段,这个字段随着记录的插入而单调递增,如果Innodb自动生成一个聚簇索引(没有显示指定主键),那这个索引包含了db_row_id字段,否则,db_row_id 这个列不会出现在任何索引中。
回滚段中的undo log 分成inser 和update 两类,insert undo log 只在事务回滚中被需要,并且可以在事务提交时丢弃这类日志。update undo log被用于一致性读,只有当没有未提交的事务时才能被丢弃,Innodb为事务分配了一个快照在一致性读时,它需要update undo log的信息来构建数据的旧的版本数据。
定期提交事务,也包含那些一致读的事务,否则innodb因为不能丢弃回滚段中的undo log会导致回滚段变得很大以致于占满表空间。
回滚段中的undo log记录大小一般小于它相应行的大小,你可以基于这个来计算你需要的回滚段大小。
Innodb多版本模式下,当你使用SQL语句删除行时它不会立刻从数据库物理删除数据,Innodb只会在丢弃相应的undo log时才会物理删除数据和索引数据,这个移除操作在Innodb中叫着purge,这个动作非常迅速,通常跟SQL的执行顺序一致。
如果插入和删除操作以差不多相同的速度进行,purge 线程会有延迟,表会变得越来越大因为表中的"dead" 行,这会导致性能被磁盘IO限制,在这种场景下,通过调整系统参数 innodb_max_purge_lag 来分配跟多的资源给purge线程。
多本版和二级索引
Innodb多版本并发控制对二级索引的处理不同于聚簇索引(primary key),聚簇索引中的记录会被就地更新,他们指向undo log的隐藏系统列可以构建旧版本数据,不同于聚簇索引,二级索引记录不包含隐藏系统列,不能被就地更新。
当二级索引被更新的时候,老的记录被标记删除,插入一条新记录,标记删除的数据最终被移除,当二级索引记录标记删除或者二级索引的页被一个新事务更新,Innodb在聚簇索引中找到这些记录,在聚簇索引中,检查这些行的 DB_TRX_ID,如果这些行在当前事务启动之后被更新了,那么就从undo log中获取老版本的数据。
当二级索引记录被标记删除或者二级索引的页被一个新事务更新,索引覆盖技术不再适用,不同于直接从索引返回数据,Innodb会从聚簇索引中寻找数据。
(翻译至MySQL官方文档)