toppic
当前位置: 首页> 奇幻小说> 《MySQL运维内参》节选 | InnoDB数据字典(下)

《MySQL运维内参》节选 | InnoDB数据字典(下)

2020-06-21 08:20:00

《MySQL运维内参》导读

  • 作者签名预售活动 | 预售进行中,五一左右发货

  • 关于《MySQL运维内参》的内容

  • 前言 | 周彦伟

  • 前言 | 王竹峰

  • 节选 | Binlog中的时间戳(上)

  • 节选 | Binlog中的时间戳(下)

  • 推荐序 | 甘泉

  • 推荐序 | 王瀚漓

  • 节选 | InnoDB数据字典(上)




书接上文,那从字典表新加载一个表的过程是如何的?看一下函数dict_load_table被精简之后的样子,如下。

UNIV_INTERN
dict_table_t*
dict_load_table(
    const char* name,   /*!< in: table name in the databasename/tablename format */
    ibool       cached, /*!< in: TRUE=add to cache, FALSE=do not */
    dict_err_ignore_t ignore_err)
{
    /* local variables ... */
    heap = mem_heap_create(32000);

    /* 这个是一个很重要的东西,在日志章节中,会做详尽的解释,此处可以忽略 */
    mtr_start(&mtr);

    /* 先找到系统表SYS_TABLES,根据这个表的表结构,拼一个记录出来。
       通过这条记录,去SYS_TABLES表中查找到想要的表 */
    sys_tables = dict_table_get_low("SYS_TABLES");
    sys_index = UT_LIST_GET_FIRST(sys_tables->indexes);

    /* 构造SYS_TABLES格式的元组 */
    tuple = dtuple_create(heap, 1);
    dfield = dtuple_get_nth_field(tuple, 0);

    /* 设置搜索列信息,因为主键是NAME列,所以直接通过这个列来搜索即可。
       找到唯一想要查找的表信息,如果没有找到,那就是没有这个表 */
    dfield_set_data(dfield, name, ut_strlen(name));
    dict_index_copy_types(tuple, sys_index, 1);

    /* 打开B+树,创建游标,对树进行搜索,搜索的模式是GE(大于等于)*/
    btr_pcur_open_on_user_rec(sys_index, tuple, PAGE_CUR_GE,
                  BTR_SEARCH_LEAF, &pcur, &mtr);

    /* 搜索完成,取出游标找到的相应记录。当然,如果没有找到的话,记录就是一个特殊值 */
    rec = btr_pcur_get_rec(&pcur);

    /* 处理没有找到,或者这条记录已经被删除(打了删除标记)的情况,
       如果记录为特殊值,则关闭游标及B+树搜索,退出,并返回NULL值 */
    if (!btr_pcur_is_on_user_rec(&pcur)
        || rec_get_deleted_flag(rec, 0)) {
        /* Not found */
        btr_pcur_close(&pcur);
        mtr_commit(&mtr);
        mem_heap_free(heap);

        return(NULL);
    }

    /* 解读找到的这条表记录,创建一个table对象,这个就是被加载的
       最原始的表信息,不包含列、索引、外健等信息 */
    err_msg = dict_load_table_low(name, rec, &table);

    innobase_format_name(table_name, sizeof(table_name), name, FALSE);

    /* 对SYS_TABLES的搜索完成,关闭游标,执行mtr_commit */
    btr_pcur_close(&pcur);
    mtr_commit(&mtr);

    /* 正如其名,加载这个表的所有列,和上面的方法是类似的,
       从SYS_COLUMNS表中加载所有关于想要查找的表的列信息 */
    dict_load_columns(table, heap);

    /* 如果需要,将这些信息加入到字典缓存中去 */
    if (cached) {
        dict_table_add_to_cache(table, TRUE, heap);
    } else {
        dict_table_add_system_columns(table, heap);
    }

    /* 同理,加载表中的所有索引信息 */
    err = dict_load_indexes(table, heap, index_load_err);

    /* 同理,加载表中的所有外键信息 */
    err = dict_load_foreigns(table->name, NULL, true, true, ignore_err);

    /* 返回完整信息的表结构 */
    return(table);
}

首先,找到SYS_TABLES,过程与找普通表是一样的,也是首先找缓存,找不到则再从系统表中加载,但这似乎是废话,因为系统表在系统启动时就加载了,不可能找不到。找到之后,构造一个查询键值,因为是通过名字查询的,同时SYS_TABLES有一个按照名字排序的索引,所以可以直接按照名字构造查询键值。然后,从B+树中查询对应记录,如果找不到则报错,找到之后,根据这个表的记录格式解析这个记录,取出ID、N_COLS、TYPE、SPACE这些基本信息,然后根据这些信息创建一个表的内存对象。到这里,这个表的自身对象就加载完成了,接着开始加载其所有列信息。

加载列操作与加载表原理基本一致,对应系统表为SYS_COLUMNS,它的聚簇索引列为TABLE_ID和POS,索引查找时,扫描记录中TABLE_ID如果是相同的,则POS是从小到大排序的,所以构造查询某一个表的所有列的键值的时候只需要通过TABLE_ID即可,扫描完成之后按照顺序取出所有列信息并一一构造内存对象,这样这个表的所有列也相应加载完成。

加载列的时候,还有一点需要注意,在InnoDB中,一个表的列包括两部分,一部分是用户创建表时指定的列,另一部分则是系统列,包括Rowid、TRXID及ROLLPTR三个列。Rowid表示记录的行号;TRXID表示这条记录最后一次被修改的事务号,主要用于事务的多版本(MVCC)管理;ROLLPTR也是用来实现多版本的,如果一条记录被某一个用户修改之后,另一个用户在这条记录不可见时,查询这条记录要找到其原来的值,那么ROLLPTR指定的就是其原来值的位置,这个位置其实就是在修改时写下的回滚记录位置。所以,对于任何一个表,其中所包括的列除了用户定义的列之外,还包括这三列。

接下来是加载这个表的索引信息,加载索引是从SYS_INDEXES中查询的,原理与上面的SYS_COLUMNS一样。这个表的关键字是TABLE_ID及索引ID,所以具有相同TABLE_ID的所有索引记录都是按照索引ID排序的。每一条记录对应一个索引,需要加载ID、名字、N_FIELDS、TYPE、PAGENO、SPACE等基本信息。

对于索引的加载,还需要加载它对应的所有关键字信息,这些信息存储在SYS_FIELDS系统表中,这个表的关键字是INDEX_ID、POS,所以一个索引的所有关键字列都是按照POS排序的。这点很重要,因为如果有多个排序列的话,顺序不同排序结果是不同的,所以一定要按照POS的值从小到大加载(B+树存储顺序),索引的关键字信息包括索引ID号、POS、列名,一个索引加载所有具有指定索引ID号的关键字列后,一个索引的加载即完成。但是加载关键字还有一点需要注意,如果一个索引不是唯一索引,则需要将表中已经加载的Rowid列以这个索引第一个关键字列的身份加载到这个索引中;如果是唯一索引,则不需要加载Rowid列,而是直接加载自身定义的列。在加载完所有的关键字列后(要么是Rowid列,要么是自字义列),还需要加载另外两个系统列,包括TRXID及ROLLPTR两个列。而对于聚簇索引,因为这个索引存储了表中所有的列,所以后面还需要加载除关键字之外的所有列,这些列是按照建表时的顺序加载的,而对于二级索引,这些列是不需要加载的。按照相同道理,将一个表中所有的索引加载完成。

上面已经叙述了所有InnoDB中系统表及索引管理的内容,但对于系统表,不能像一般想象的普通表那样,用户并不能查询其中的内容,只能是系统内部自己管理及维护,比如SYS_TABLES等这些表,MySQL是不承认的。这个问题也与MySQL的特性有关,因为它是插件式的,必须兼容所有的存储引擎,但并不是每个存储引擎都有这些系统表,所以,用户是不能通过MySQL来直接查询或者访问InnoDB系统表的。

另外一个问题需要重点强调一下,我们在上面讲述时,重点讲述了表、列、索引等信息,但在表对象的上一层,即库信息,有没有相应的数据字典表呢?这里我想说的是,没有。

上面介绍的函数dict_load_table中,第一个参数是用来指定表名的,对这个参数的注释说明是这样的:/ * !\< in: table name in the databasename/tablename format */,可以看到,这个名字传入的时候,就是以格式databasename/tablename传入的,并且在构造搜索记录时,直接用了这个参数。也就是说,一个表所属的库信息,在InnoDB的字典表中是存储于SYS_TABLES表中name列的,并没有额外的表来存储库信息。所以可能有很多人想知道的InnoDB中表与库之间关系的维系方式,其实就是在这里。

当然,这是从数据字典的方面考虑的。如果是MyISAM情况下,没有数据字典怎么办?这个问题就很明确了,MyISAM压根就没有这些信息,也没有对应关系,或者说唯一的对应关系就是它被放到哪一个目录名下了,它所在的目录名,即为所在的数据库名。也正因为这样,一个MyISAM数据库表,可以通过COPY元数据文件和数据文件来迁移。MyISAM是完全独立的,但InnoDB就做不到了,因为InnoDB表的相关信息还存储在ibdata中。

也正因为这些原因,如果有想要重命名一个数据库的需求,可行的办法有下面两种。

  1. 如果是MyISAM,就直接在离线情况下,在文件系统的层面,直接重命名所在目录名,即可实现这个需求;而对于InnoDB存储引擎,即使是离线状态下,也不能通过这种方式来改库名,因为库名存储在了数据字典中,需要在线修改。

  2. 如果要在在线的情况下(这种方式,所有存储引擎都是可以的),实现这个需求,那就需要通过一个迂回的方式来做了,取出一个库中所有的表,然后拼一大堆语句出来,首先创建一个新的库,比如newdb,而老的库名为olddb,接着就是执行很多rename table olddb.table to newdb.table。将所有表都执行这样的操作之后,老库下面就没有表了,而新库下面就包括了所有的表,这样就变相地实现了重命名库的需求。


Rowid管理

在InnoDB中,用户表中的记录不一定都会有一个Rowid列,Rowid只有在一个表没有定义主键时,也就是需要Rowid作为聚簇索引列的时候才会被分配给这个表,而Rowid的管理分配,并不是一个表独享一个ID空间,而是全局的,所有表都共享这个ID号。

一般情况下,或是像上面的TABLEID、INDEXID一样,每次分配一个就更新一次字典页面的值,但对于Rowid并不是这样,因为插入操作比创建一个表或者索引的操作频繁多了,每次都去修改未免对效率影响太大,所以InnoDB也做了相应的优化,就是每分配一个Rowid,系统只是在内存中加1,不会修改页面,只有当这个值为256的倍数时才会写入一次。那么自然会想到,如果插入200次,这些值还没有被写入,这时系统重启了,岂不是ID号会重复使用?这个当然不是问题,因为数据库启动时,调用的函数dict_boot中还做一个工作,就是将上次写入的Rowid值向上对齐256后再加上256,这样就不会有问题了,大不了可能会跳过很多ID号导致这个值增长太快而已。

总结

之前章节也提到过,关于MySQL的数据字典,目前还有些问题需要改进,而目前也正在做。InnoDB对于MySQL来说,会越来越重要,从目前的发展就可以看得出来,两者之间的关系变得越来越紧密,如此一来,Server层和引擎层(这里专指InnoDB)的合作就越生硬,会从以前InnoDB很“委屈”地去配合Server层,变为Server层和InnoDB相互依赖,相互配合,两层之间的交互变得越来越多,很明显可以看出来,Server层现在对InnoDB有了特别的“照顾”,也可以说,MySQL和InnoDB现在已经进入了一个蜜月期。

像这样发展下去,它们联合起来之后,体现在功能上就是,一方(InnoDB)的强大由于另一方(MySQL Server)的局限而不能体现出来的局面越来越少,而整体的表现就是MySQL会越来越强大。拿数据字典管理来说,有可能出现的改进就是数据字典功能更强大,可以包含更丰富的信息,随之而来,表的功能和配合也会更丰富,并且可以做到DDL的回滚功能,从而在熟知的ALTER TABLE方面,有更加友好的功能及更安全的在线改表方案。

另一个值得期待的功能就是InnoDB的数据字典信息可以在MySQL层面看得到、摸得着,从而给运维带来一种新的思路,新的展现方式,而不是通过一个危险的information_schema来统计库表相关信息。有人可能说,这可能实现吗?谁知道呢,实现了不是更好吗?



欢迎关注DBAce,感谢支持《MySQL运维内参》,本书正在进行作者签名预售中,请点击阅读原文查看。




友情链接