IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    MySQL 8 新特性体验之自增持久化

    朝阳发表于 2016-10-25 11:03:54
    love 0

    自增变量一直以来都是MySQL上引以为傲的一个特性。虽然和Oracle的 Sequence相比没有后者灵活,但是使用比Sequence更为简单方便。

    但是,很多时候的但是总是让人不爽,这里的这个但是也同样会给人带来不爽。当使用InnoDB存储引擎的时候,每次重启都会重新计算出当前表上自增字段实际最大值,并作为下一次分配的起点。这就代表如果大量删除过数据之后,自增值可能被重用。这在很多场景下可能导致问题,包括但不限于:主备切换、历史数据迁移等场景。

    MySQL 8 将自增值记录进入了redo log,每次重启的时候,通过redo log来恢复上一次的最大值。

    InnoDB使用一个引擎私有的系统表+特殊redo log的方式,在引擎内部自己解决问题。其大概思路为:

    1. 当发现索引损坏时,写入一条redo log,但不更新数据词典
    2. 引入一个innodb引擎私有的系统表,称为DD Buffer Table,每次checkpoint之前会将索引corruption bit存入其中。
    3. 在崩溃恢复时,同时从redo log和DD Buffer Table中读取索引 corruption bit, 合并结果,并标记内存中的表和索引对象。

    在该worklog中解决的是corruption bit的持久化问题,但实现的框架也适用于其他目的,例如update_time, auto_inc, count(*)等,因此对代码做了通用性的抽象。

    初始化Persister

    目前Persister的类型仅有两种,一个用于corruption bit的持久化,一个用于自增列的持久化,对应的类为:

    Persister:
        |-- CorruptedIndexPersister
        |-- AutoIncPersister
    

    Persister对应全局对象dict_persist_t::persisters,可以通过类型persistent_type_t来找到对应的Persister,目前仅有PM_INDEX_CORRUPTED及PM_TABLE_AUTO_INC,但从注释来看,未来肯定会做更多的扩展

    Persister在启动时调用函数dict_persist_init进行初始化。

    新的系统表

    新的系统表名为SYS_TABLE_INFO_BUFFER,对应管理类为DDTableBuffer,指针存储在dict_persist->table_buffer中。

    Table id为DICT_TBL_BUFFER_ID,值为0xFFFFFFFFFF000000ULL, ROOT PAGE是ibdata的第8个page(FSP_TBL_BUFFER_TREE_ROOT_PAGE_NO)

    系统表包含两个列:TABLE_ID及BLOB类型的METADATA(ref DDTableBuffer::init),METADATA列包含了所有需要持久化的元数据。

    更新Metata

    当发现索引损坏时,调用dict_set_corrupted标记索引损坏,并进行日志写入(Persister::write_log):

    • 写入的内容包含space id 和index id
    • 日志格式为:
    | 1byte: type = MLOG_TABLE_DYNAMIC_META
    | Table ID
    | 1byte: SUB-TYPE: PM_INDEX_CORRUPTED
    | 1byte: Num: Number of corrupted indexs
    | 4bytes: space id
    | 8bytes: index id
    
    ## 这个结构有点奇怪,理论上同一个表的索引应该存在于同一个space中,这里只需要记录一个space id就可以了
    

    写完这条日志后,会进行一次log flush,将日志持久化到磁盘。由于index corruption属于低概率事件,不会引起性能问题。

    然后再设置表的状态为脏 (dict_table_mark_dirty),这里为表定义了三种状态:

    dict_table_t::dirty_status
    
    METADATA_CLEAN: 在DDTableBuffer表中没有任何缓存数据
    METADATA_BUFFERED: 在DDTableBuffer系统表中存在至少一行数据,未来需要回写到DD中
    METADATA_DIRTY: 一些持久化元数据在内存中被修改,需要回写到DDTableBuffer中
    
    如果当前表状态为METADATA_CLEAN,则需要将对象加到全局链表dict_persist_t::dirty_dict_tables中,这个链表用于维护状态为METADATA_DIRTY或者METADATA_BUFFERED的表对象
    
    

    dirty_status在调用dict_table_mark_dirty后被设置成METADATA_DIRTY,并确保在dict_persist_t::dirty_dict_tables链表上

    而对于AUTOINC列的持久化发生在插入或者更新时,注意对于临时表无需做持久化。

    在插入聚集索引记录前(row_ins_clust_index_entry_low), 会先从entry中把counter拿出来,并记入日志

    在更新记录时(row_upd_clust_rec),如果表上有autoinc列并且被更新成更大的值(row_upd_check_autoinc_counter),也会去尝试记录写日志。

    持久化AUTOINC的日志写入函数为AutoIncLogMtr::log,当新的counter大于已经持久化的dict_table_t::autoinc_persisted时,将autoinc_persisted更新为新的counter,并将表的diry_status置为dirty(如果需要的话)

    记录的日志格式为

    | 1byte: type = MLOG_TABLE_DYNAMIC_META
    | Table ID
    | 1byte: Sub-type: PM_TABLE_AUTO_INC
    | Autoinc Counter
    

    注意这里在写入日志后,出于性能考虑并没有做flush log操作,因此如果crash了,已分配的autoinc不能保证不被重用,但从用户的角度来看(事务级别),autoinc是不会重用的。

    回写DDTableBuffer

    有几种情况会将内存修改回写到DDTableBuffer中:

    1. 在做checkpoint(log_checkpoint)之前,所有在dirty_dict_tables链表上的表对象,对应persist metadata都需要回写到DDTableBuffer中(dict_persist_to_dd_table_buffer)
    2. 从内存中驱逐一个表对象时(dict_table_remove_from_cache_low),如果需要的话也会去尝试回写。
    3. 在对包含自增列的表做DDL后,需要持久化counter,在如下函数中,会调用dict_table_set_and_persist_autoinc:
    ha_innobase::commit_inplace_alter_table
    create_table_info_t::initialize_autoinc()
    row_rename_table_for_mysql;
    
    

    回写的过程也比较简单(dict_table_persist_to_dd_table_buffer_low):

    1. 通过表对象初始化需要回写的Metadata数据: corrupt index及autoinc值(dict_init_dynamic_metadata)
    2. 构建记录值,插入DDTableBuffer系统表(DDTableBuffer::replace(), 如果记录存在的话,则进行悲观更新操作
    3. 表对象的diry_status修改成 METADATA_BUFFERED,表示有buffer的元数据

    恢复与启动

    在崩溃恢复时,当解析到日志MLOG_TABLE_DYNAMIC_META时(MetadataRecover::parseMetadataLog),会进行解析并将解析得到的数据存储到集合中(MetadataRecover::m_tables),如果存在相同table-id的项,就进行替换,确保总是最新的。

    在完成recovery后,搜集到的meta信息暂时存储到srv_dict_metadata中, 随后进行apply(srv_dict_recover_on_restart), apply的过程也比较简单,载入表对象,然后对表对象进行更新(MetadataRecover::apply),例如对于autoinc列,就总是选择更大的那个值。

    注:本文参考了云栖社区上关于MySQL8中对于该特性在源码级别内容介绍相关文章



沪ICP备19023445号-2号
友情链接