关于MySQL ERROR 1146 Table doesnt exist的解析

来源:转载


☉源码版本 5.7.14


在MYSQL使用innodb的时候我们有时候会看到如下报错:


ERROR1146(42S02):Table'test.test1bak'doesn'texist

首先总结下原因:
缺少frm文件
innodb数据字典不包含这个表

我们重点讨论情况2,因为情况1是显而易见的。


在使用innodb存储引擎的时候某些时候我们show tables能够看到这个表,但是如果进行任何操作会报错如下:


mysql>showtables;
|test1bak|
mysql>desctest1bak;
ERROR1146(42S02):Table'test.test1bak'doesn'texist

也许你会说我明明能够看到这个表啊,为什么访问还会报错呢?其实要清楚innodb有自己的数据字典,只要有frm 文件存在show tables就能看到,但是最终是否能够正常打开表结构在innodb中还依赖于innodb的数据字典,主要的包含:


1、INNODB_SYS_columns


2、INNODB_SYS_FIELDS


3、INNODB_SYS_TABLES


4、INNODB_SYS_INDEXES


如果报错出现我们需要首先查看的是INNODB_SYS_TABLES是否包含了这个表的信息。也许在这些数据字典中也许某些列并显示并不是那么明确,比如


mysql>select*frominformation_schema.innodb_sys_tableswherename='test/kkkkm1';
+----------+-------------+------+--------+-------+-------------+------------+---------------+------------+
|TABLE_ID|NAME|FLAG|N_COLS|SPACE|FILE_FORMAT|ROW_FORMAT|ZIP_PAGE_SIZE|SPACE_TYPE|
+----------+-------------+------+--------+-------+-------------+------------+---------------+------------+
|374|test/kkkkm1|33|6|540|Barracuda|Dynamic|0|Single|
+----------+-------------+------+--------+-------+-------------+------------+---------------+------------+

比如这里的FLAG列为33,他实际上是一个位图表示方式,分别表示如下信息:


/*TableandtablespaceflagsaregenerallynotusedfortheAntelopefile
formatexceptfortheloworderbit,whichisuseddifferentlydependingon
wheretheflagsarestored.
====================Loworderflagsbit=========================
|REDUNDANT|COMPACT|COMPRESSEDandDYNAMIC
SYS_TABLES.TYPE|1|1|1
dict_table_t::flags|0|1|1
FSP_SPACE_FLAGS|0|0|1
fil_space_t::flags|0|0|1
/**WidthoftheCOMPACTflag*/
#defineDICT_TF_WIDTH_COMPACT1
/**WidthoftheZIP_SSIZEflag*/
#defineDICT_TF_WIDTH_ZIP_SSIZE4
/**WidthoftheATOMIC_BLOBSflag.TheAntelopefileformatsbrokeup
BLOBandTEXTfields,storingthefirst768bytesintheclusteredindex.
Barracudarowformatsstorethewholeblobortextfieldoff-pageatomically.
Secondaryindexesarecreatedfromthisexternaldatausingrow_ext_t
tocachetheBLOBprefixes.*/
#defineDICT_TF_WIDTH_ATOMIC_BLOBS1
/**IfatableiscreatedwiththeMYSQLoptionDATADIRECTORYand
innodb-file-per-table,anolderenginewillnotbeabletofindthattable.
Thisflagpreventsolderenginesfromattemptingtoopenthetableand
allowsInnoDBtoupdate_create_info()accordingly.*/
#defineDICT_TF_WIDTH_DATA_DIR1
/**WidthoftheSHAREDtablespaceflag.
Itisusedtoidentifytablesthatexistinsideasharedgeneraltablespace.
IfatableiscreatedwiththeTABLESPACE=tsnameoption,anolderenginewill
notbeabletofindthattable.Thisflagpreventsolderenginesfromattempting
toopenthetableandallowsInnoDBtoquicklyfindthetablespace.*/
#defineDICT_TF_WIDTH_SHARED_SPACE1

接下来我们分析一下为什么是FLAG是33如下:


33的二进制为00100001从低位开始
1:从源码注释来看本位COMPACT/COMPRESSED/DYNAMIC均为1
0000: ZIP_SSIZE flag 这四位用于支持压缩功能如COMPRESSED
1:ATOMIC_BLOBS flag 这一位是COMPACT和DYNAMIC主要区别所在,请看源码注释
0:DATA DIRECTORY and innodb-file-per-table flag为了支持DATA DIRECTORY语法
0:SHARED tablespace flag为了支持TABLESPACE语法

然后我们测试一下:


如果我们建立如下的表:
CREATE TABLE t2 (c1 INT PRIMARY KEY) TABLESPACE = innodb_file_per_table
DATA DIRECTORY = '/root/mysql5.7.14/percona-server-5.7.14-7/mysql-test/var/mysqld.1';
其type为97二进制为01100001:使用DATA DIRECTORY建立使用ATOMIC_BLOBS且无压缩则DYNAMIC格式
详见:15.5.5 Creating a File-Per-Table Tablespace Outside the Data Directory
如果我们建立如下的表:
CREATE TABLESPACE tt1 ADD DATAFILE '/root/mysql5.7.14/tt1.ibd';
CREATE TABLE tsh (c1 INT ) TABLESPACE tt1 ROW_FORMAT=COMPACT ;
其type为129二进制为 10000001:使用TABLESPACE语法建立不使用ATOMIC_BLOBS且无压缩则为COMPACT格式
详见:15.5.9 InnoDB General Tablespaces

我们可以看到使用8位一个字节而已就可以表示出大量的信息,这也是位图的优势,其他比如 MTYPE/PRTYPE也是这种表示方式


接下来我们回到主题,需要看看这个错到底是哪里报错来的?进行trace后如下,我们来看看主要部分:


注意这里的trace是mysql debug版本下查看函数调用的主要方法参考官方文档26.5.1.2 Creating Trace Files502 [email protected]: | | | | | | | | | | | >ha_innobase::open_dict_table


[email protected]:||||||||||||>dict_table_open_on_name
[email protected]:|||||||||||||dict_table_open_on_name:table:'test/test1bak'
[email protected]:|||||||||||||>dict_table_check_if_in_cache_low
[email protected]:||||||||||||||dict_table_check_if_in_cache_low:table:'test/test1bak'
[email protected]:|||||||||||||<dict_table_check_if_in_cache_low125
[email protected]:|||||||||||||>dict_load_table
[email protected]:||||||||||||||dict_load_table:loadingtable:'test/test1bak'
[email protected]:||||||||||||||>dict_table_check_if_in_cache_low
[email protected]:|||||||||||||||dict_table_check_if_in_cache_low:table:'test/test1bak'
[email protected]:||||||||||||||<dict_table_check_if_in_cache_low125
[email protected]:||||||||||||||>dict_load_table_one
[email protected]:|||||||||||||||dict_load_table_one:table:test/test1bak
[email protected]:|||||||||||||||>dict_table_check_if_in_cache_low
[email protected]:||||||||||||||||dict_table_check_if_in_cache_low:table:'SYS_TABLES'
[email protected]:|||||||||||||||<dict_table_check_if_in_cache_low125
[email protected]:|||||||||||||||>btr_cur_search_to_nth_level
[email protected]:|||||||||||||||<btr_cur_search_to_nth_level2005
[email protected]:||||||||||||||<dict_load_table_one3084
[email protected]:|||||||||||||<dict_load_table2882
[email protected]:||||||||||||<dict_table_open_on_name1292
[email protected]:|||||||||||<ha_innobase::open_dict_table6676
[email protected]:|||||||||||>sql_print_warning
[email protected]:||||||||||||>error_log_print
[email protected]:|||||||||||||>print_buffer_to_file
[email protected]:||||||||||||||enter:buffer:InnoDB:Cannotopentabletest/test1bakfromtheinternaldatadictionaryofInnoDBthoughthe.frmfileforthe
tableexists.Pleaserefertohttp://dev.mysql.com/doc/refman/5.7/en/innodb-troubleshooting.htmlforhowtoresolvetheissue.
[email protected]:|||||||||||||<print_buffer_to_file2332
[email protected]:||||||||||||<error_log_print2357
[email protected]:|||||||||||<sql_print_warning2384

☉其实大概步骤就是


1、Checks if a table is in the dictionary cache


根据dict_sys->table_hash寻找


2、Loads a table definition and also all its index definitions.


通过扫描字典的B+树进行加载


3、如果不能找到则报错


这样也就解释了为什么show tables能够看到但是select却报错Table doesn't exist ,而从原理上讲show tables只是查看了frm文件。


另外这里也提一个案列,曾经有一个朋友问我他将整个库目录都拷贝了,但是表能看到但是一操作就报Table doesn't exist,显然他没有拷贝ibdata1,数据字典的引导信息都存在这里面文件的第7个page中,其b+树也是存在其中,用源码解释一下:


/**********************************************************************//**
Getsapointertothedictionaryheaderandx-latchesitspage.
@returnpointertothedictionaryheader,pagex-latched*/
dict_hdr_t*
dict_hdr_get(
/*=========*/
mtr_t*mtr)/*!<in:mtr*/
{
buf_block_t*block;
dict_hdr_t*header;
block=buf_page_get(page_id_t(DICT_HDR_SPACE,DICT_HDR_PAGE_NO),
univ_page_size,RW_X_LATCH,mtr);
header=DICT_HDR+buf_block_get_frame(block);
buf_block_dbg_add_level(block,SYNC_DICT_HEADER);
return(header);
}

注意这里的 DICT_HDR_SPACE, DICT_HDR_PAGE_NO分别是宏定义


/*Spaceidandpagenowherethedictionaryheaderresides*/
#defineDICT_HDR_SPACE0/*theSYSTEMtablespace*/
#defineDICT_HDR_PAGE_NOFSP_DICT_HDR_PAGE_NO
#defineFSP_DICT_HDR_PAGE_NO7/*!<datadictionaryheader
page,intablespace0*/

space 0就是ibdata1的space_no,7当然就是引导块,这哥们连ibdata1都没拷贝,当然innodb数据字典自然不包含这些表了。其实也是上面描述的原理 。


那么正确的拷贝的方式一定是停机后,整个数据目录进行拷贝,而不是仅仅拷贝需要的库的目录,否则innodb数据字典是不能正常加载的。


最后附带space 0的部分块解释
/*--------------------------------------*/
#defineFSP_XDES_OFFSET0/*!<extentdescriptor*/
#defineFSP_IBUF_BITMAP_OFFSET1/*!<insertbufferbitmap*/
/*Theibufbitmappagesaretheoneswhose
pagenumberisthenumberaboveplusa
multipleofXDES_DESCRIBED_PER_PAGE*/
#defineFSP_FIRST_INODE_PAGE_NO2/*!<ineverytablespace*/
/*Thefollowingpagesexist
inthesystemtablespace(space0).*/
#defineFSP_IBUF_HEADER_PAGE_NO3/*!<insertbuffer
headerpage,in
tablespace0*/
#defineFSP_IBUF_TREE_ROOT_PAGE_NO4/*!<insertbuffer
B-treerootpagein
tablespace0*/
/*Theibuftreerootpagenumberin
tablespace0;itsfseginodeisonthepage
numberFSP_FIRST_INODE_PAGE_NO*/
#defineFSP_TRX_SYS_PAGE_NO5/*!<transaction
systemheader,in
tablespace0*/
#defineFSP_FIRST_RSEG_PAGE_NO6/*!<firstrollbacksegment
page,intablespace0*/
#defineFSP_DICT_HDR_PAGE_NO7/*!<datadictionaryheader
page,intablespace0*/
****/*--------------------------------------*/****

分享给朋友:
您可能感兴趣的文章:
随机阅读: