MySQL8.0对数据字典进行了重构,用户表、数据字典表和其他MySQL系统表的元数据都存储在mysql库的数据字典表中。在mysql库中,除了general_log和slow_log这两个日志表,其他所有表的存储引擎都是InnoDB,有了它,DDL终于可以支持原子操作了。以DROPTABLEt1,t2为例,不会出现t1表删除成功而t2表删除失败的情况,而是要么删除成功,要么删除失败。在这篇文章中,我们来谈谈MySQL8.0中的数据字典表。本文内容基于MySQL8.0.29源码。一、概述MySQL8.0对数据字典进行重构后,取消了MySQL5.7中用于保存元数据的磁盘文件:.frm、.par、.TRN、.TRG、.isl、db.opt、ddl_log.log。如果想知道上述磁盘文件中存储了哪些元数据,可以参考MySQL官方文档:https://dev.mysql.com/doc/refman/8.0/en/data-dictionary-file-removal。HTML。这些文件被废除后,原来保存在这些文件中的元数据保存在数据字典表中。数据字典表本身也发生了很大变化:数据字典表不再位于InnoDB系统表空间,而是迁移到了mysql库中,mysql库位于mysql表空间中,磁盘文件为mysql同上。SYS_TABLES、SYS_COLUMNS、SYS_INDEXES、SYS_FIELDS这四张数据字典表不再完全依赖源代码中硬编码的元数据,而是和其他表一样,使用mysql库的数据字典表中存储的元数据。上面四个数据字典表的名称也发生了变化,后面会介绍。2.Whatarethedatadictionarytables?按照官方文档的定义,MySQL8.0一共有31张数据字典表:dd_propertiesinnodb_ddl_logcatalogscharacter_setscheck_constraintscollationscolumn_statisticscolumn_type_elementscolumnseventsforeign_key_column_usageforeign_keysindex_column_usageindex_partitionsindex_statsindexesparameter_type_elementsparametersresource_groupsroutinesschematast_spatial_reference_systemstable_partition_valuestable_partitionstable_statstablestablespace_filestablespacestriggersview_routine_usageview_table_usage上面只是简单列出了数据字典表的表名,如果想了解每个表存放了什么内容,可以参照官方文档:https://dev.mysql.com/doc/refman/8.0/en/system-schema.html.Bydefault,wecannotseethedatadictionarytable,andweneedtomeetthefollowingconditionstoseeit:compiletheDebugversionofMySQLfromthesourcecode,takecmakecompilationasanexample,youneedtobringthe-DCMAKE_BUILD_TYPE=Debugcompilationoption.连接MySQL后,先执行如下SQL,让MySQL跳过数据字典表的权限检查:SETSESSIONdebug='+d,skip_dd_table_access_check'满足以上两个条件后,执行以下SQL,查看所有数据字典表已经:SELECTa.nameASdb_name,b.*FROMmysql.schemataASaINNERJOINmysql.tablesASbONa.id=b.schema_idWHEREb.schema_id=1ANDb.hidden='System'ORDERBYb.id执行上述SQL列出了32张表,其中innodb_dynamic_metadata表不属于数据字典表。上面列出的数据字典表中,有4个需要强调一下,因为不管是数据字典表本身还是用户表,这4个表是分不开的:tables:存放表的元数据,包括表空间ID,DatabaseID、表ID、表名、表注释、行格式等信息对应MySQL5.7中的数据字典表SYS_TABLES。columns:存储表中字段的元数据,包括表ID、字段ID、字段名称、字段注释、字段类型、是否自增等,对应MySQL5.7中的数据字典表SYS_COLUMNS。indexes:存放表的索引元数据,包括表空间ID、表ID、索引ID、索引名称、索引注释、是否为隐藏索引等信息,对应MySQL5.7中的数据字典表SYS_INDEXES.index_column_usage:存储索引中字段的元数据,包括索引ID、字段ID、索引中字段的编号(从1开始)、索引字段的长度(如果是前缀索引字段,则索引字段的长度)prefix),索引字段排序,是否隐藏,一共有6个字段,对应MySQL5.7中的数据字典表SYS_FIELDS。此表不包含更详细的字段信息。如果需要,可以通过列表的字段ID获取。index_column_usage和SYS_FIELDS表不完全一样,有2点需要说明:index_column_usage包含6个字段,比SYS_FIELDS多3个字段:order:表示索引字段的排序。length:当hidden=0时,表示索引字段的长度,或者前缀索引字段的前缀长度;当hidden=1时,字段值为NULL。hidden:0表示索引中的字段由用户定义;1表示索引中的字段是MySQL添加的。下面是一张测试表。图中的name是columns表中的表查询得到的,其他都是index_column_usage表的字段。CREATETABLE`t5`(`id`intunsignedNOTNULLAUTO_INCREMENT,`str1`varchar(255)CHARACTERSETutf8mb3COLLATEutf8_general_ciNOTNULLDEFAULT'',`i1`intNOTNULLDEFAULT'0',`str2`varchar(255)CHARACTERSETutf8mb3COLLATEutf8_general_ciNOTNULLDEFAULT'',`i2`intNOTNULLDEFAULT'0',PRIMARYKEY(`id`)USINGBTREE,UNIQUEKEY`idx_i1`(`i1`)使用BTREE,KEY`idx_str1`(`str1`)使用BTREE)ENGINE=InnoDBDEFAULTCHARSET=utf8mb3;index_id=310为主键索引,hidden=0的记录为主键字段;hidden=1的记录是主键索引中的其他字段,即表字段中的字段。index_id=312为二级索引,其中str1为前缀索引字段,前缀长度为255*3(utf8一个字符占用的最大字节数)=765,hidden=0表示str1为用户自定义二级索引字段;hidden=1的记录是MySQL自己在二级索引中添加的主键字段。index_column_usage表中的ordinal_position表示编号,从1开始;SYS_FIELDS中的POS表示序号,从0开始。除了在Debug版本的MySQL中设置权限检查跳过数据字典表外,还可以通过information_schema数据库中的表或视图查看对应的数据字典表:数据字典表information_schema表或视图表INNODB_TABLEScolumnsINNODB_COLUMNSindexesINNODB_INDEXESindex_column_usageINNODB_FIELDS……………3、数据字典表的元数据在哪里?数据字典表用于存储用户表的元数据。这个很好理解,因为在创建用户表的时候,所有的数据字典表都已经存在了,可以将用户表的各种元数据插入到对应的数据字典表中。向上。数据字典表本身的元数据也会保存在数据字典表中,但是在创建数据字典表的时候,有些数据字典表还没有创建,这是一个问题。我们以列和索引这两个数据字典表为例:先创建列表,再创建索引表。列表创建成功后,需要在索引表中保存索引元数据,但索引表还没有创建。列表的索引元数据自然不能保存在索引表中。MySQL解决这个问题的方法是引入一个中间层来临时存储所有数据字典表的各种元数据。数据保存到对应的数据字典表中。这里所谓的中间层其实就是一个存储适配器。源码中对应的类名为Storage_adapter,是一个实现单例模式的类。在MySQL初始化数据目录的过程中,Storage_adapter类的实例属性m_core_registry是所有数据字典表元数据的临时存放处。4.创建数据字典表。在我们安装好MySQL之后,我们想让MySQL运行起来。首先要做的是初始化MySQL,其实就是初始化MySQL的数据目录。初始化过程会创建MySQL运行时需要的各种表空间、数据库和表,包括数据字典表。创建数据字典表的过程分为三个步骤:第一步,在System_tables类的实例属性m_registry中注册代表每个数据字典表的Object_table对象。除了数据字典表之外,m_registry还包含mysql库中的其他MySQL系统表。第二步:循环m_registry中的所有表,通过Object_table获取数据字典表的DDL,然后调用dd::execute_query()执行DDL语句创建数据字典表。dd::execute_query()在创建数据字典表的过程中,会将该表的元数据暂存在Storage_adapter类的实例属性m_core_registry中,而不会保存在各种元数据对应的数据字典表中。这样做的原因在上一节介绍数据字典表的元数据在哪里的时候已经介绍过了,这里不再赘述。dd::execute_query()执行一个数据字典表的DDL语句后,数据字典表已经存在于表空间中,处理完m_registry中的所有表后,数据字典表全部存在。第三步,循环遍历m_registry中的所有表,将每张表的元数据(数据库ID、表ID、表名、注释、字段数等)保存到mysql.tables数据字典表中,然后保存表字段、索引等元数据保存到对应的数据字典表中。所有数据字典表的元数据都是从Storage_adapter类的实例属性m_core_registry中读取的。经过三个步骤的共同努力,所有数据字典表的元数据都保存在数据字典表中。通过引入外力(m_core_registry)解决了先有鸡还是先有蛋的问题。5.打开数据字典表。数据字典表存储了MySQL运行过程中需要的一系列关键数据。它经常被使用。在MySQL启动过程中,数据字典表的元数据会被加载到内存中。这是开表。的过程。即打开数据字典表是在MySQL启动时完成的。前面我们提到,数据字典表的元数据也存储在数据字典表中。MySQL在启动过程中,必须先打开数据字典表才能获取数据字典表的元数据,而要获取数据字典表的元数据,则必须先打开数据字典表。这个过程很曲折,不容易理解。我们打个比方:数据字典表就是一个房间,数据字典表的元数据就是打开房间门的钥匙。现在问题来了,因为MySQL在数据字典表中保存了数据字典表的元数据,相当于把开房门的钥匙留在了房间里。想要开房就得先拿到钥匙,想要拿到钥匙就得先开房。通过这种转换,问题是不是更好理解了?我们先想想怎么解决房间和钥匙的问题。如果开房钥匙落在房间里,有什么解决办法?我能想到的有以下3种解决方案:暴力破解,撬锁。找一个专业的开锁器来开锁。使用备用钥匙开门。这种方法是最好的,但有一个前提:事先准备好一把备用钥匙。MySQL没有前两种方案,而是保留一个备用key,即第三种方案。接下来看一下MySQL打开数据字典表的过程:第1步,和创建数据字典表一样,把代表每个数据字典表的Object_table对象注册到System_tables类的实例属性m_registry中。每个数据字典表的Object_table对象定义了这个表的表名、字段、索引、外键等信息。Object_table对象中保存的不是DDL语句,而是类似于我们建表时的DDL语句。下面这个例子是源代码中表空间数据表mysql.tablespacesObject_table对象中定义的该表的信息:Tablespaces::Tablespaces(){m_target_def.set_table_name("tablespaces");m_target_def.add_field(FIELD_ID,"FIELD_ID","idBIGINTUNSIGNEDNOTNULLAUTO_INCREMENT");m_target_def.add_field(FIELD_NAME,"FIELD_NAME","nameVARCHAR(268)NOTNULLCOLLATE"+String_type(Object_table_definition_impl::name_collat??ion()->m_coll_name));m_target_def.add_field(FIELD_OPTIONS,"FIELD_OPTIONS","选项MEDIUMTEXT");m_target_def.add_field(FIELD_SE_PRIVATE_DATA,"FIELD_SE_PRIVATE_DATA","se_private_dataMEDIUMTEXT");m_target_def.add_field(FIELD_COMMENT,"FIELD_COMMENT","commentVARCHAR(2048)NOTNULL");m_target_def.add_field(FIELD_ENGINE,"FIELD_ENGINE","engineVARCHAR(64)NOTNULLCOLLATEutf8_general_ci");m_target_def.add_field(FIELD_ENGINE_ATTRIBUTE,"FIELD_ENGINE_ATTRIBUTE","engine_attributeJSON");m_target_def.add_index(INDEX_PK_ID,"INDEX_PK_ID","PRIMARYKEY(id)");公吨arget_def.add_index(INDEX_UK_NAME,"INDEX_UK_NAME","UNIQUEKEY(name)");}步骤2,循环m_registry中的所有表,通过Object_table获取数据字典表的DDL,然后调用dd::execute_query()执行DDL创建数据字典表的语句与第二步不同创建数据字典表。dd::execute_query()执行DDL,并不真正创建表,而是生成数据字典表元数据,并将元数据保存到Storage_adapter类的实例属性m_core_registry中。m_core_registry中保存的数据字典表元数据就是我们前面提到的备用键。有了这把备用钥匙,就可以打开数据字典表了。第3步,循环遍历m_registry中的所有表,通过第2步生成的数据字典表元数据,读取mysql表空间(表空间文件:mysql.ibd)中每个数据字典表的元数据。这一步完成后,将所有数据字典表的元数据加载到内存中,并打开数据字典表。第四步,循环遍历m_registry中的所有表,从m_core_registry中删除数据字典表的元数据。第五步:循环遍历m_registry中的所有表,将从表空间中读取的数据字典表的元数据存储到m_core_registry中。不过,这一步存入m_core_registry的并不是所有数据字典表的元数据,而是22个核心(CORE)数据字典表的元数据:catalogscharacter_setscheck_constraintscollationscolumn_statisticscolumn_type_elementscolumnsforeign_key_column_usageforeign_keysindex_column_usageindex_partitionsindexesresource_groupsschematatable_partition_valuestable_partitionstablestablespace_filestablespacestriggersview_routine_usageview_table_usage1~5步执行完成之后,m_core_registry中就只包含上面22个核心Themetadataofthedatadictionarytablesisnowavailable.Withthemetadataofthesetables,allothertablescanbeopened.Step6,calldd::execute_query()toexecuteFLUSHTABLEStocloseallopeneddatadictionarytablesandnon-datadictionarytables,andthenusethemetadatareadfromthedatadictionarytablestoopendatadictionarytablesandothersAllneededtablesareincluded.Sofar,thegeneralprocessofopeningthedatadictionarytablehasbeenintroduced,andyoumayhavedoubts:inthesecondstep,calldd::execute_query()toexecuteDDL,andthemetadataofthedatadictionarytablehasbeenobtained.Inordertodistinguish,themetadataobtainedhereiscalledalternatemetadata.Step3Openthedatadictionarytableaccordingtothealternatemetadata,andreadthemetadataofthedatadictionarytablefromthetablespace.Alsoinordertodistinguish,themetadataobtainedhereiscalledtheoriginalmetadata.Step4removesalternatemetadatafromm_core_registry.Step5Storeoriginalconfigurationmetadataintom_core_registry.Isn'tthesparemetadataofthedatadictionarytablethesameastheoriginalmetadata?Whyevenreplacethebackupmetadatawiththeoriginalmetadata,isitsuperfluous?Ididnotcomparewhetherthebackupmetadataisexactlythesameastheoriginalmetadataonebyone,thisisnotasmallproject.However,sincethisisimplementedinthesourcecode,thereshouldbeareasonforit,butIhaven'tfoundityet.IfIfindthereasonbehindit,Iwilladdittomyblog.6.小结要理解MySQL8.0中的数据字典表,核心是理解以下两点:在初始化数据目录时,数据字典表中数据字典表的元数据是如何存储的?这主要是借助于Storage_adapter类实例的m_core_registry属性。在创建数据字典表的过程中,首先创建每一张数据字典表,将元数据暂存在m_core_registry中。所有数据字典表创建成功后,最后将所有数据字典表的元数据保存到对应的数据字典表中。MySQL启动时,如何用数据字典表的元数据打开数据字典表?这利用了源代码中硬编码的数据字典表定义和Storage_adapter类实例的m_core_registry属性。MySQL启动过程中,首先通过Object_table获取创建数据字典表的DDL,调用dd::execute_query()执行DDL,获取metadata(备用原始数据),将备用的metadata暂存在m_core_registry属性中,然后通过备用元数据打开数据字典表。本文转载自微信公众号“一树一溪”,可通过以下二维码关注。转载本文请联系艺书艺熙公众号。
