首页 数据库 mysql教程 MySQL源码:Innobase字典管理及索引_MySQL

MySQL源码:Innobase字典管理及索引_MySQL

Jun 01, 2016 pm 01:49 PM
记录

bitsCN.com

前言:最近在重温innobase的B树及记录模块,发现对之前已经看过的字典管理及索引内容竟然忘却了,所以重新看了一遍并将它记录下来,防止哪天又给忘了。不过还是那句话,如果哪些有问题还请指正。谢谢。

 

    在innobase中,最基本的有四个系统表,用来存储用户定义的表、列、索引及索引列等信息,这些表分别为SYS_TABLES、SYS_COLUMNS、SYS_INDEXES、SYS_FIELDS。每一个表的列分别如下:

1)、SYS_TABLES,用来存储所有的以innobase为存储引擎的表,每条记录对应已经定义的一个表。

NAME:表示一个表的表名。

ID:表示这个表的ID号(8个字节)。

N_COLS:表示这个表的列的个数,建表指定的列数(4个字节)。

TYPE:表示这个表的存储类型,包括记录的格式、压缩等信息(4个字节)。

MIX_ID:不清楚有什么用,好像没用。

MIX_LEN:不清楚有什么用,好像没用。

CLUSTER_NAME:不清楚有什么用,好像没用。

SPACE:表示这个表所在的表空间ID号(4字节)。

这个表对应的主键为(NAME),同时还有一个在ID号上的唯一索引。

2)、SYS_COLUMNS,用来存储innobase中定义的所有表的所有列的信息,每一个列对应这个表中的一条记录。

TABLE_ID:表示这个列所属的表的ID号(8字节)。

POS:表示这个列在表中是第几个列(4字节)。

NAME:表示这个列的列名。

MTYPE:表示这个列的主数据类型(4字节)。

PRTYPE:表示这个列的一些精确数据类型,它是一个组合值,包括NULL标志、是否是有符号数的标志、是否是二进制字符串的标志及表示这个列是真的VARCHAR(数据存储用两个字节)(4字节)。

LEN:表示这个列的数据长度,但不包括VARCHAR类型,因为这个类型是在记录里面存储了数据长度(4字节)。

PREC:表示这个列数据的精度,但目前好像没有使用(4字节)。

这个表的主键列为(TABLE_ID、POS)。

3)、SYS_INDEXES,用来存储innobase中所有表的索引信息,每条记录对应一个索引。

TABLE_ID:表示这个索引所属的表的ID号(8字节)。

ID:表示这个索引的索引ID号(8字节)。

NAME:表示这个索引的索引名。

N_FIELDS:表示这个索引的索引的个数(4字节)。

TYPE:表示这个索引的类型,包括聚簇索引、唯一索引、DICT_UNIVERSAL、DICT_IBUF(插入缓冲区B树)(4字节)。

SPACE:表示这个索引数据所在的表空间ID号(4字节)。

PAGE_NO:表示这个索引对应的B树的根页面(4字节)。

这个表的主键列为(TABLE_ID、ID)。

4)、SYS_FIELDS,用来存储所有索引中定义的索引列,每一条记录对应一个索引列。

INDEX_ID:这个列所在的索引(8字节)。

POS:这个列在某个索引中是第几个索引列(4字节)。

COL_NAME:这个索引列的列名。

这个表的主键列为(INDEX_ID、POS)。

       从上面的字典表就可以了解到,innobase是如何管理表、索引、列及关键字的,那么这几个表是如何加载的呢?首先从建库说起。

       在innobase启动的时候,如果是要初始化库,则需要创建字典管理的B树等信息,则在innobase_start_or_create_for_mysql中调用dict_create函数来创建,因为innobase中的系统表的结构、个数等都是固定的,不会有人修改,所以在初始化库的时候只需要创建这几个表的存储B树(与上面所说的索引对应,一个索引一个B树)即可,同时将这几个B树的根页号存储在一个固定的位置,而不需要将这几个表的自身信息存储在系统表中了。在innobase中,专门有一个页面(0号表空间0号文件的7号页面)是用来管理字典信息的,这个页面用来存储上面提到的这四个表的五根页面号,以及下一个表ID值(全局变量,创建新表时的ID号从这里分配,每分配一个,这个ID号要加1)、下一个索引ID值(创建索引时的ID号从这里分配,每分配则加1)、下一个表空间ID值(与上同理)、ROWID(这个是表中的ROWID号,这个在下面另做介绍)这四个值。上面这些操作通过函数dict_hdr_create实现,这里面通过btr_create创建SYS_TABLES的两个索引;SYS_COLUMNS的一个索引;SYS_INDEXES的一个索引;SYS_FIELDS的一个索引。

       那么在innobase创建完B树及一些其它初始化操作之后,就通过函数dict_boot加载常驻内存的四个系统表及读取一些其它信息,上面已经提过,系统表的个数及结构都是不会被修改的,所以直接通过固定的硬编码构建这几个表即可,比如:

创建SYS_TABLES:

    table = dict_mem_table_create("SYS_TABLES", DICT_HDR_SPACE, 8, 0);

    dict_mem_table_add_col(table, heap, "NAME", DATA_BINARY, 0, 0);

    dict_mem_table_add_col(table, heap, "ID", DATA_BINARY, 0, 0);

    dict_mem_table_add_col(table, heap, "N_COLS", DATA_INT, 0, 4);

    ……

创建SYS_TABLES表的索引:

    index = dict_mem_index_create("SYS_TABLES", "CLUST_IND",

                      DICT_HDR_SPACE,

                      DICT_UNIQUE | DICT_CLUSTERED, 1);

    dict_mem_index_add_field(index, "NAME", 0);

过程大致如上面所述。

初始化之后,因为它是常驻内存的,这四个表是挂在一个全局的字典结构中的:

struct dict_sys_struct{

    mutex_t         mutex;

    row_id_t            row_id;

    hash_table_t*   table_hash;

    hash_table_t*   table_id_hash;

    UT_LIST_BASE_NODE_T(dict_table_t)       table_LRU;  /*!

    ulint           size;

    dict_table_t*   sys_tables; /*!

    dict_table_t*   sys_columns;    /*!

    dict_table_t*   sys_indexes;    /*!

    dict_table_t*   sys_fields; /*!

};

       很明显可以看到,结构体中最下面的四个就是用来存储对应的四个字典表的,其中第一个mutex自不用说,先说一下这个ROWID的管理,在innobase中,用户表中的记录不一定都会有一个ROWID,ROWID只有在一个表没有定义主键的时,也就是ROWID自己作为索引列的时候这个表才会被分配ROWID,而这个ROWID也不是一个表独享一个ID空间,而是全局的,所有表都共享这个ID号,一般想来,或是像上面的TABLEID、INDEXID一样,每次分配一个就更新一次字典页面的值,但对于ROWID并不是这样,因为插入操作可比创建一个表或者索引频繁多了,每次都去修改未免对效率影响太大,所以innobase也做了相应的优化,就是每当分配一个ROWID,系统只是在内存中加1,不会修改页面,而只有当这个值为256的倍数时才会写入一次,那么自然会想到,如果插入200次,这个值还没有被写入,如果系统重启了,岂不是ID号会重复使用,这个当然不是问题,也就是在上面dict_boot中还做一个工作,就是将上次写入的ROWID值向上对齐256后再加上256,这样就不会有问题了,大不了可能会跳过很多ID号导致这个值增长太快而已。

       上面结构体中的下面三个链表及HASH表是用来存储innobase中所有表的缓存的,包括系统表、用户创建的表,这里有两个HASH,一个是按照名字缓存的,一个是按照ID来缓存的,各为其用罢。LRU链表用来管理表对象的缓存的,涉及到淘汰操作。

       上面介绍了字典对象存储及管理之后,下面介绍一下普通用户表的一个加载过程,当用户访问一个表时,如前面写的一篇《表对象的字典缓存》中介绍,系统首先会从表对象缓存池中找这个表的SHARE对象,如果找到则直接在实例化的并且空间链表中拿一个实例化的表对象出来使用即可,如果没有一个可用的实例化对象,则需要重新打开(实例化这个表),在实例化这个表的时候就需要找到这个表的字典信息,包括这个表本身、列信息及索引信息等,这个操作就通过我们下面介绍的主体dict_table_get来实现。

       这个函数里面的实现就是找到指定表的上面所述信息,找得过程是:首先从字典缓存中寻找,看这个表有没有被缓存在上面提到的HASH表中,如果找到则直接拿去使用即可,如果没有找到则通过dict_load_table从上面提到的四个系统表中加载,首先找到SYS_TABLES,过程与找普通表是一样的,也是首先找缓存,找不到则再从系统表中加载,但这似乎是废话,因为系统表在系统启动时就加载了,不可能找不到的,找到之后构造一个查询键值,因为是通过名字查询的,同时SYS_TABLES有一个按照名字排序的搜引,所以直接按照名字构造查询键值即可,然后从B树中查询对应记录,如果找不到则报错,找到之后,根据这个表的记录格式解析这个记录,取出ID、N_COLS、TYPE、SPACE这些基本的信息,然后根据这些信息创建一个表的内存对象,到这里这个表的自身对象加载完成,接着要加载其所有列信息。

       加载列操作与上面所述原理基本一致,找到系统表SYS_COLUMNS,因为这个表的聚簇索引是表ID及POS,所以在B树内,如果多个记录的表ID是相同的,则所以POS是从小到大排序的,所以构造查询键值的时候只需要构造表ID即可,将找到的所有列信息按照取出顺序依次加载每条记录的对应信息即可,将所有的具体相同表ID的记录加载完成后,这个表的所有列也相应加载完成。这里还有一点需要注意,在innobase中,一个表的列包括两部分,一部分是用户创建表时指定的列,另一部分则是系统列,包括ROWID、TRXID及ROLLPTR三个列,ROWID表示记录的行号,上面已经提到过,TRXID表示这条记录最后一次被修改的事务号,主要用于事务的多版本(MVCC)管理,ROLLPTR也是用来实现多版本的,如果一个记录被某一个用户修改之后,另一个用户在这条记录不可见时,查询这条记录时要找到其原来的值,那么ROLLPTR指定的就是其原来值的位置,这个位置其实就是在修改时写下的回滚记录的位置。所以对于任何一个表,其中所包括的列除了用户定义的列之外,还包括这三个列。

       接下来就加载这个表的索引信息,加载索引是从SYS_INDEXES中查询的,原理与上面的SYS_COLUMNS是一样的,这个表的关键字是表ID及索引ID,所以具有相同表ID的所有索引记录都是按照索引ID排序的,那么对于每一条记录都对应一个索引,需要加载ID、名字、N_FIELDS、TYPE、PAGENO、SPACE等基本信息,对于索引的加载,还需要加载它对应的所有关键字信息,这些信息存储在SYS_FIELDS系统表中,这个表的关键字是INDEXID、POS,所以一个索引的所有关键字列都是按照POS排序的,这点很重要,因为如果有多个排序列的话,顺序不同排序结果是不同的,所以一定要按照POS的值从小到大加载(B树存储顺序),索引的关键字信息包括索引ID号、POS、列名,一个索引加载所有具有指定索引ID号的关键字列后一个索引的加载即完成,但是加载关键字还有一点需要注意,如果一个索引不是唯一索引,则需要将表中已经加载的ROWID列以这个索引的第一个关键字列的身份加载到这个索引中,如果是唯一索引,则不需要加载ROWID列,而是直接加载自身定义的列。在加载完所有的关键字列后(要么是ROWID列,要么是自字义列),还需要加载另外两个系统列,包括TRXID及ROLLPTR两个列。而对于聚簇索引,因为这个索引存储了表中所有的列,所以后面还需要加载除关键字之外的所有列,这些列是按照建表时的顺序加载的,而对于二级索引,则这些列是不需要加载的。按照相同道理将一个表中所有的索引加载完成。

       到此一个表的加载就完成了,那么从上面就可以看出一个索引中加载的列的信息及顺序关系。总结如下:

       聚簇唯一索引:

       [有序的关键字列][TRXID][ROLLPTR][其它建表创建的非关键字列]

       比较列个数:有序的关键字列个数

       聚簇非唯一索引:

       [ROWID][TRXID][ROLLPTR][其它建表创建的非关键字列]

       比较列个数:只ROWID一列而已

       二级唯一索引或二级非唯一索引(聚簇索引为唯一索引):

       [有序的索引列][剩余聚簇索引顺序列][TRXID][ROLLPTR]

       比较列个数:有序的关键字列个数加剩余聚簇索引顺序列个数

       二级唯一索引或二级非唯一索引(聚簇索引为非唯一索引):

       [有序的索引列] [ROWID][TRXID][ROLLPTR]

       比较列个数:有序的关键字列个数加ROWID

       可以看出,对于innobase这样的记录格式定义,在比较两个记录时,只需要从第0个列开始,对比比较列个数的列信息即可,使用记录的比较非常容易。上面对于聚簇索引为唯一索引的二级索引,其有可能是在本身聚簇索引的某个或某些列上建立的,也有可能是在其它列上建立的,那么其索引列的比较列就是将建立索引时指定的索引列按照顺序放在最前面,根着就是聚簇索引中的剩余的其它所有列,顺序还是原来的顺序。因为二级索引的作用就是快速的在二级索引中找到相应的记录之后还可以快速的去聚簇索引中快速的找到原记录,那么二级索引中定义的比较列就是为了快速的找到聚簇索引的,因为这些列中包括了所有的聚簇索引的索引列,并且顺序与聚簇索引一致。那么如果二级索引的聚簇索引是非唯一索引,则二级索引中用于快速索引聚簇索引记录的索引键就是ROWID本身。

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

       总结:innobase的字典及索引记录等的设计还是很方便、很先进的。值得学习深思借鉴。

bitsCN.com
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

拼多多买过的东西在哪里查看记录 查看买过的商品记录的方法 拼多多买过的东西在哪里查看记录 查看买过的商品记录的方法 Mar 12, 2024 pm 07:20 PM

  拼多多软件内提供的商品好物非常多,随时随地想买就买,而且每一件商品质量都是严格把关的,件件商品都是正品,不同还有非常多优惠的购物折扣,让大家网购根本停不下来。输入手机号在线登录,在线添加多个收货地址和联系方式,可以随时查看最新的物流动态,不同品类的商品板块都是开放的,搜索上下滑动选购下单,足不出户轻松体验便捷的网购服务,还能查看所有的购买记录,包括自己买过的商品,数十个购物红包、优惠券免费领取使用,现在小编在线详细为拼多多用户们带来查看买过的商品记录的方法。  1.打开手机,点击拼多多图标,

如何在iPhone中检查通话记录并将其导出? 如何在iPhone中检查通话记录并将其导出? Jul 05, 2023 pm 12:54 PM

iPhone中的通话记录经常被低估,并且是iPhone最关键的功能之一。凭借其简单性,此功能具有至关重要的意义,可以提供有关在设备上拨打或接听的呼叫的重要见解。无论是出于工作目的还是法律诉讼,访问通话记录的能力都被证明是无价的。简单来说,通话记录是指每当拨打或接听电话时在iPhone上创建的条目。这些日志包含关键信息,包括联系人的姓名(如果未另存为联系人,则为号码)、时间戳、持续时间和呼叫状态(已拨打、未接或未接听)。它们是您的通信历史记录的简明记录。通话记录包括存储在iPhone上的通话记录条

如何查看和管理 Linux 命令历史记录 如何查看和管理 Linux 命令历史记录 Aug 01, 2023 pm 09:17 PM

如何在Linux中查看命令历史记录在Linux中,我们使用history命令来查看所有以前执行的命令的列表。它有一个非常简单的语法:history与历史记录命令配对的一些选项包括:选项描述-c清除当前会话的命令历史记录-w将命令历史记录写入文件-r从历史记录文件重新加载命令历史记录-n限制最近命令的输出数量只需运行history命令即可在Linux终端中查看所有以前执行的命令的列表:除了查看命令历史记录之外,您还可以管理命令历史记录并执行修改先前执行的命令、反向搜索命令历史记录甚至完全删除历史记

如何在iPhone上的健康应用程序中查看您的用药日志历史记录 如何在iPhone上的健康应用程序中查看您的用药日志历史记录 Nov 29, 2023 pm 08:46 PM

iPhone可让您在“健康”App中添加药物,以便跟踪和管理您每天服用的药物、维生素和补充剂。然后,您可以在设备上收到通知时记录已服用或跳过的药物。记录用药后,您可以查看您服用或跳过用药的频率,以帮助您跟踪自己的健康状况。在这篇文章中,我们将指导您在iPhone上的健康应用程序中查看所选药物的日志历史记录。如何在“健康”App中查看用药日志历史记录简短指南:前往“健康”App>浏览“>用药”>用药“>选择一种用药>”选项“&a

C#开发建议:日志记录与监控系统 C#开发建议:日志记录与监控系统 Nov 22, 2023 pm 08:30 PM

C#开发建议:日志记录与监控系统摘要:在软件开发过程中,日志记录与监控系统是至关重要的工具。本文章将介绍C#开发中日志记录与监控系统的作用和实施建议。引言:在大型软件开发项目中,日志记录和监控是必不可少的工具。它们可以帮助我们实时了解程序运行状况,快速发现并解决问题。本文将讨论C#开发中如何使用日志记录和监控系统,以提高软件质量和开发效率。日志记录系统的作用

如何进行Java开发项目的日志记录与监控 如何进行Java开发项目的日志记录与监控 Nov 03, 2023 am 10:09 AM

如何进行Java开发项目的日志记录与监控一、背景介绍随着互联网的快速发展,越来越多的企业开始进行Java开发,构建各种类型的应用程序。而在开发过程中,日志记录和监控是一个不可忽视的重要环节。通过日志记录与监控,开发人员可以及时发现和解决问题,保证应用程序的稳定性和安全性。二、日志记录的重要性1.问题追踪:在应用程序出现错误时,日志记录可以帮助我们快速定位问题

如何在iPhone上清除历史记录 如何在iPhone上清除历史记录 Jun 29, 2023 pm 01:13 PM

如何在Safari中清除iPhone历史记录?要清除Apple的Safari上的浏览和搜索历史记录,您需要在设备上打开“设置”应用程序。选择“设置”后,您需要向下滚动并选择Safari,然后将显示另一个菜单,您需要选择清除历史记录和网站数据。您现在需要从菜单中选择清除历史记录和数据,这将从Apple的Safari浏览器中删除所有搜索历史记录,浏览历史记录,cookie和数据。就是这样,所有以前的浏览历史记录和搜索历史记录现在都已从Safari中删除。如果您不想删除Safari中的所有搜索历史记录

keep怎么记录跑步公里 记录跑步轨迹在哪里 keep怎么记录跑步公里 记录跑步轨迹在哪里 Mar 12, 2024 am 11:10 AM

  我们都知道上面对于我么来说都是非常不错的运动类型的软件,可以实时的帮助用户们完成各种运动都可以,而且我们在跑步的一些过程之中也能看到对于上面的一些轨迹都是可以多多的了解得到的,很多用户们对于上面的一些功能资讯都不了解,所以今天小编就来给你好好的讲解其中的一些内容体验,让大家们都可以更好的进行多方面的一些选择,如果你也想知道对于自己跑步方面的一些轨迹和记录的话,一定不要错过,更多优质的内容都在等着你们来了解,超多有趣的攻略资讯都在等着你们,如果你们也喜欢想知道的话,现在就和小编一起来看看吧。 

See all articles