学习笔记:InnoDB存储结构及多版本实现

因为InnoDB是多版本化的数据库存储引擎, 它必须在表空间中保存关于旧版本数据行的信息。这个信息被存在名为rollback segment(类似于Oracle中的回滚段)的数据结构中。

在内部,InnoDB给数据库中的每一行添加三个域。一个是6字节的DB_TRX_ID域,用来说明插入或更新该行的最后一个事务的事务识别符。同时,删除操作也被内部处理为更新操作,其中行中一个特殊的位被设置用来标注该行已删除。每一行也包含一个称为回滚指针的7字节DB_ROLL_PTR域。回滚指针指向一个写在回滚段中的撤销日志记录。如果该行被更新,撤销日志记录包含重建该行被更新之前的内容必需的信息。还有一个6字节的DB_ROW_ID域包含在新行被插入时单调增加的Row ID,如果InnoDB自动生成聚集索引,索引中就会包含这个Row ID。否则,这个DB_ROW_ID不会出现在任何索引中。

InnoDB使用在回滚段中的信息来执行事务回滚中需要的撤销操作。它也使用这个信息来为一个持续读构建数据行的早期版本。

在回滚段中的撤销日志被分为插入和更新两种。插入撤销日志仅在事务回滚中需要,且只要事务一提交就可以被丢弃。更新撤销日志也被用在持续读中,而且它们仅在没有被InnoDB分配给一个事务快照之后才能被丢弃,因为这个快照在持续读中可能会需要撤销日志的信息来建立一个数据行的早期版本。

你必须记得有规律地提交你的事务,包括那些只包含持续读的事务。否则, InnoDB不能从更新撤销日志丢弃数据,并且回滚段可能变得太大,填满你的表空间。

在一个回滚段里,一个撤销日志记录的物理尺寸小于相应的已插入行或已更新行。你可以用这个信息来计算回滚段需要的空间。(如果是插入,那么记录那个行的id号到回滚段就ok了,如果要删除,直接通过id号来定位行即可删除它,实现回滚.如果是一般的更新,那么直接记录对应的行id和被更新的字段即可.但是如果是删除操作,innodb只需要存储一条删除日志到回滚段中,比如只包含rowid信息,回滚的时候,只需要把对应的rowid的标记位deleted删除即可.)

在InnoDB多版本化方案中,当你用SQL语句删除一行时,该行没有被从数据库立即物理删除掉。只有当InnoDB可以丢弃用于删除操作的撤销日志记录时,InnoDB才从数据库物理删除相应行以及它的索引记录。这个删除操作被成为净化(Purge,有点类似于Postgresql中的autovacuum),它运行得很快,通常与做删除的SQL语句花的时间在一个数量级。(这种方式还是非常先进的,理由有2: a.对于删除操作,完全可以把删除的行全部移动到回滚日志中,而不是标记为删除.当需要回滚的时候,直接把这些行执行插入操作即可回滚;而需要提供一致读的时候,直接去回滚段中查找即可----这显然浪费了大量的IO,所以innodb不这么做. b.这种做法还节省了磁盘空间.试想如果把删除的信息都存储到回滚段中,一个delete * from t该要产生多少回滚日志?浪费多少磁盘空间?而且当事务回滚的时候,把这些东西从回滚段拷贝到磁盘,又是多么浪费时间? 当回滚段被重用的时候,innodb会先删除回滚日志中的条目,然后把这些回滚日志指向的被标记为deleted的行也purge掉. oracle的做法是删除的时候将整行数据放入回滚段,所以oracle中事务回滚的成本是很高的)

在某种情景下,用户以几乎相同的比率,小批次地在表中插入和删除行,净化线程可能会滞后,并且表变得越来越大,所有操作都由于磁盘约束变得非常慢。即使表只有10MB有用的数据,它也可能被死行占据10GB空间。在这种情况下,节流新操作,并分配更多的资源来净化线程会比较好。全局系统变量innodb_max_purge_lag就是为此而生的。

posted @ 2012-01-13 23:41  wwh  阅读(321)  评论(0编辑  收藏  举报