您的位置:首页 > 数据库 > MySQL

MySQL中InnoDB全文检索

2016-01-29 16:01 676 查看
InnoDB存储引擎从1.2.x开始支持全文索引技术,其采用fullinvertedindex的方式。在InnoDB存储引擎中,将(DocumentID,Postition)视为一个ilist。因此在全文检索的表中,有两个列,一个是word字段,一个是ilist字段。并且在word字段上有设索引。此外,由于InnoDB存储引擎在ilist字段上存放了Position信息,故可以进行ProximitySearch,而MyISAM不支持该特性

如之前所说,倒排索引需要将word存放在一个表中,这个表称为AuxiliaryTable(辅助表)在InnoDB存储引擎中,为了提高全文检索的并发性。共有6张AuxiliaryTable,每张表根据word的Latin编码进行分区

AuxiliaryTable是持久的表,存放在磁盘上,然而在InnoDB存储引擎的全文索引中,还有另外一个重要的概念FTSIndexCache(全文检索索引缓存),其用来提高全文检索的性能

FTSIndexCache是一个红黑树结构,其根据(word,ilist)进行排序,这意味着插入的数据已更新了对应的表,但是对全文索引的更新可能在粉刺操作后还在FTSIndexCache中,AuxiliaryTable可能没有更新。InnoDB存储引擎会批量对AuxiliaryTable进行更新.而不是每次插入后更新一次AuxiliaryTable.当全文检索进行查询时,AuxiliaryTable首先会将在FTSIndexCache中对应的word字段合并到AuxiliaryTable中,然后进行查询。这种merge操作非常类似之前的InsertBuffer功能。不同的是InsertBuffer是个持久性的对象,并且是B+树结构,然后FTSIndexCache的作用又和InsertBuffer类似,它提高了InnoDB存储引擎的性能,并且由于其根据红黑树排序后进行批量插入,其产生的AuxiliaryTable相对较小

InnoDB存储引擎允许用户查看指定倒排索引的AuxiliaryTable分词的信息,可以通过设置innodb_ft_aux_table来观察倒排索引的AuxiliaryTable下面的SQL语句设置查看test架构下表fts_a的AuxiliaryTable:

SETGLOBALinnodb_ft_aux_table='test/fts_a';


可以在information_schema架构下的表INNODB_FT_INDEX_TABLE得到表fts_a中的分词信息。

对于InnoDB存储引擎而言,其总是在事务提交时将分词写入到FTSIndexCache,然后通过批量写入到磁盘。虽然InnoDB存储引擎通过一种延时的、批量的写入方式来提高数据库的性能,但是上述操作仅在事务提交时发生。

当数据库关闭时,在FTSIndexCache中的数据库会同步到磁盘上的AuxiliaryTable中。如果当数据库发生宕机时,一些FTSIndexCache中的数据可能未同步到磁盘上,那么下次重启数据库时,当用户对表进行全文检索(查询、插入)时,InnoDB存储引擎会自动读取未完成的文档,然后进行分词操作,再将分词结果放到FTSIndexCache

为了支持全文检索,必须有一个列与word进行映射。在InnoDB中这个列被命名成FTS_DOC_ID,其类型为BIGINTUNSIGNEDNOTNULL,并且InnoDB存储引擎自动会在该列加上一个名为FTS_DOC_ID_INDEX的UniqueIndex.这些操作由存储引擎自己完成,用户也可以在建表时自动添加FTS_DOC_ID,以及对应的UniqueIndex。由于列名FTS_DOC_ID聚友特殊意义,因此在创建时必须注意相应的类型,否则会报错



可以看到,由于用户手动定义FTS_DOC_ID为INT,而非BIGINT因此在创建时候会抛出异常,应该将此处修改成对应的BIGINT即可

文档中的分词的插入操作是在事务提交时完成,但是对于删除操作,其在事务提交时,不删除磁盘AuxiliaryTable的记录,而只是删除FTSCacheIndex记录,对于AuxiliaryTable中被删除的记录,存储引擎会记录其FTSDOCUMENTID,并将其保存在DELETEauxiliarytable中,在设置参数innodb_ft_aux_table后,用户可以访问information_schema架构下的表INNODB_FT_DELETED来观察删除的FTSDocumentID

由于文档的DML操作实际并不删除索引中的数据,相反还会在对应的DELETED表中插入记录,因此随着应用程序的允许,索引会变得越来越大,即使索引中的有些数据已经被删除,查询也不会选择这类记录,为此,InnoDB提供了一种方式,允许用户手工将已删除的记录从索引中彻底删除,这就是OPTIMIZETABLE。因为OPTIMIZETABLE还会进行一些其他的操作。如Cardinality重新统计,若用户希望对倒排索引进行操作,可以通过innodb_optimize_fulltext_only设置

SETGLOBALinnodb_optimize_fulltext_only=1;

OPTIMIZETABLEfts_a;


若被删除的文档很多,那么OPTIMIZETABLE操作可能占用非常多的时间,会影响到程序并发性,并极大的降低用户的响应时间,用户可以通过参数innodb_ft_num_word_optimize来限制每次实际删除的分词数量,默认为2000

CREATETABLEfts_a(
FTS_DOC_IDBIGINTUNSIGNEDAUTO_INCREMENTNOTNULL,
bodyTEXT,
PRIMARYKEY(FTS_DOC_ID)
);

INSERTINTOfts_aSELECTNULL,'peaseporridgeinthepost';
INSERTINTOfts_aSELECTNULL,'peaseporridgehot,peaseporridgecold';
INSERTINTOfts_aSELECTNULL,'Ninedaysold';
INSERTINTOfts_aSELECTNULL,'Somelikeithot,somelikeitcold';
INSERTINTOfts_aSELECTNULL,'Somelikeitthepot';
INSERTINTOfts_aSELECTNULL,'Ninedaysold';
INSERTINTOfts_aSELECTNULL,'Ilikecodedays';

CREATEFULLTEXTINDEXidx_ftsONfts_a(body);


查看数据

mysql>select*fromfts_a;
+------------+----------------------------------------+
|FTS_DOC_ID|body|
+------------+----------------------------------------+
|1|peaseporridgeinthepost|
|2|peaseporridgehot,peaseporridgecold|
|3|Ninedaysold|
|4|Somelikeithot,somelikeitcold|
|5|Somelikeitthepot|
|6|Ninedaysold|
|7|Ilikecodedays|
+------------+----------------------------------------+
7rowsinset(0.00sec)


mysql>setglobalinnodb_ft_aux_table='iot2/fts_a';
QueryOK,0rowsaffected(0.00sec)

mysql>SELECT*FROMinformation_schema.`INNODB_FT_INDEX_TABLE`;
+----------+--------------+-------------+-----------+--------+----------+
|WORD|FIRST_DOC_ID|LAST_DOC_ID|DOC_COUNT|DOC_ID|POSITION|
+----------+--------------+-------------+-----------+--------+----------+
|code|7|7|1|7|7|
|cold|2|4|2|2|34|
|cold|2|4|2|4|30|
|days|3|7|3|3|5|
|days|3|7|3|6|5|
|days|3|7|3|7|12|
|hot|2|4|2|2|15|
|hot|2|4|2|4|13|
|like|4|7|3|4|5|
|like|4|7|3|4|17|
|like|4|7|3|5|5|
|like|4|7|3|7|2|
|nine|3|6|2|3|0|
|nine|3|6|2|6|0|
|old|3|6|2|3|10|
|old|3|6|2|6|10|
|pease|1|2|2|1|0|
|pease|1|2|2|2|0|
|pease|1|2|2|2|19|
|porridge|1|2|2|1|6|
|porridge|1|2|2|2|6|
|porridge|1|2|2|2|19|
|post|1|1|1|1|22|
|pot|5|5|1|5|17|
|some|4|5|2|4|0|
|some|4|5|2|4|17|
|some|4|5|2|5|0|
+----------+--------------+-------------+-----------+--------+----------+
27rowsinset(0.00sec)

可以看到每个word对应一个DOC_ID和POSITION。此外,还记录了FIRST_DOC_ID、LAST_DOC_ID、DOC_COUNT分别代表该word第一次出现文档的ID,最后一次出现的文档ID,以及该word在多少个文档中存在。

若此时执行下面的SQL语句,会删除FTS_DOC_ID为7的文档

DELETEFROMfts_aWHEREFTS_DOC_ID=7;


InnoDB存储引擎并不会直接删除索引中对应的记录,而是将删除的文档ID插入到DELETED表

SELECT*FROMinformation_schema.`INNODB_FT_DELETED`;




如果用户想要彻底删除倒排索引中该文档的分词信息,可以

mysql>SETGLOBALinnodb_optimize_fulltext_only=1;
QueryOK,0rowsaffected(0.00sec)

mysql>OPTIMIZETABLEfts_a;
+------------+----------+----------+----------+
|Table|Op|Msg_type|Msg_text|
+------------+----------+----------+----------+
|iot2.fts_a|optimize|status|OK|
+------------+----------+----------+----------+
1rowinset(0.08sec)

mysql>SELECT*FROMinformation_schema.`INNODB_FT_DELETED`;
+--------+
|DOC_ID|
+--------+
|7|
+--------+
1rowinset(0.00sec)

mysql>SELECT*FROMinformation_schema.`INNODB_FT_BEING_DELETED`;
+--------+
|DOC_ID|
+--------+
|7|
+--------+
1rowinset(0.00sec)


运行OPTIMIZETABLE可以将记录彻底删除,并且彻底删除的文档ID会记录到INNODB_FT_BEGIN_DELETED中。此外,由于7这个文档一倍删除,因此不允许在插入这个文档ID,否则会抛出异常

mysql>INSERTINTOfts_aSELECT7,'Ilikethisdays';
ERROR182(HY000):InvalidInnoDBFTSDocID


stopword列表(stopwordlist)是本节最后阐述的一个概念,其表示该列表中的word不需要对其进行索引分词操作。例如,对于the这个单词,由于其不具有具体的意义,因此将其视为stopword,InnoDB存储引擎有一张默认的stopword列表,在information_schema架构下,表名为INNODB_FT_DEFAULT_STOPWORD,默认为36个stopword可以通过参数innodb_ft_server_stopword_table来定义stopword列表,如

mysql>CREATETABLEinnodb_ft_bug(
->valueVARCHAR(18)NOTNULLDEFAULT''
->)ENGINE=INNODBDEFAULTCHARSET=utf8;#此处必须为utf8不然会碰到bug
QueryOK,0rowsaffected(0.07sec)

mysql>SETGLOBALinnodb_ft_server_stopword_table='iot2/innodb_ft_bug';
QueryOK,0rowsaffected(0.00sec)


遇到bug的情形

mysql>CREATETABLEuser_stopword(VALUEVARCHAR(30))ENGINE=INNODB;
QueryOK,0rowsaffected(0.03sec)
mysql>SETGLOBALinnodb_ft_server_stopword_table='iot2/user_stopword';
ERROR1231(42000):Variable'innodb_ft_server_stopword_table'can'tbesettothevalueof'iot2/user_stopword'

观察错误日志提示

InnoDB:invalidcolumnnameforstopwordtableiot2/user_stopword.Itsfirstcolumnmustbenamedas'value'.



使用全文检索还有以下限制

每张表只能有一个全文检索的索引

由多列组合而成的全文检索的索引必须使用相同的字符集与排序规则

不支持没有单词界定符delimiter的语言,如中文日文汉语等
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: