高级特性
全文索引
MySQL5.1中全文索引的变化
在MySQL5.1中引入了一些和全文索引相关的改进,包括一些性能上的提升和新增插件式的解析,通过此用户可以自己定制增强搜索功能。例如,插件可以该笔那索引文本的方式。可以用更灵活的方式进行分词(例如,可以指定C++作为一个单独的词语)、预处理可以对不同的文档类型进行索引(如PDF),还可以做一些自定义的词干规则。插件还可以直接影响全文搜索的工作方式——例如,直接使用词干进行搜索。
全文索引的限制和替代方案
MySQL的全文索引实现有很多的设计本身带来的限制。在某些场景下这些限制是致命的,不过也有很多办法绕过限制。例如,MySQL全文索引中只有一种判断相关性的方法:词频。索引也不会记录索引词在字符串中的位置,所以位置也就无法用在相关性上。虽然大多数情况下,尤其是数据量很小的时候,这些限制都不会影响使用,但也可能不是你锁想要的。而且MySQL的全文索引也没有提供其他可选的相关性排序算法(它无法存储基于相对位置的相关性排序数据)。数据量的大小也是一个问题。MySQL的全文索引只有全部在内存的时候,性能才非常好。如果内存无法装在全部索引,那么搜索速度可能会非常慢。当你使用精确短语搜索时,想要好的性能,数据和索引都需要在内存中。相比其他的索引类型,当INSERT、UPDATE和DELETE操作进行时,全文索引的操作代价都很大:
- 1.修改一段文本中的100个单词,需要100词索引操作,而不是一次
- 2.一半来说列长度并不会太影响其他的索引类型,但是如果是全文索引,三个单词的文本和10 000个单词的文本,性能可能会相差几个数量级
- 3.全文索引会有更多的碎片,可能需要做跟更多的OPTIMIZE TABLE操作
全文索引还会影响查询优化器的工作。索引选择、WHERE子句、ORDER BY都有可能不是按照你所预想的方式来工作:
- 1.如果查询中使用了MATCH AGAINST子句,而对应列上又有可用的全文索引,那么MySQL就一定会使用这个全文索引。这时,即使有其他列的索引可以使用,MySQL也不会比较到底哪个索引的性能更好。所以,即使这时有更合适的索引可以使用,MySQL仍然会置之不理
- 2.全文索引只能用作全文搜索匹配。任何其他操作,如WHERE条件比较,都必须在MySQL完成全文搜索返回记录后才能进行。这和其他普通索引不同,例如,在处理WHERE条件时,MySQL可以使用普通索引一次-判断多个表达式。
- 3.全文索引不存储索引列的实际值。也就不可能用作索引覆盖扫描
- 4.除了相关行排序,全文索引不能用作其他的排序。如果查询需要做相关性以外的排序操作,都需要使用文件排序。
让我们看看这些限制如何影响查询语句。来看一个例子,假设有一百万个文档记录,在文档的作者author字段上有一个普通的索引,在文档内容字段content上有全文索引。先在我们要搜索作者123,文档中又包含特定词语的文档。很多人可能会按照下面的方式来写查询语句
... WHERE MATCH(content) AGAINST('High Performance MySQL') AND author = 123;
而实际上,这样做的效率非常低。因为这里使用MATCH AGAINST,而且恰好上面有全文索引,所以MySQL优先选择使用全文索引,即先搜索所有的文档,查找是否有包含关键词的文档,然后返回记录看看作者是否是123.所以这里也就没有使用author字段上的索引。一个替代方案时将author列包含到全文索引中。可以在author列的值前面附上一个不常见的前缀,然后将这个带前缀的值存放到一个单独的filters列中,并单独维护该列(也许可以使用触发器来做维护工作)。
... WHERE MATCH(content, filters) AGAINST ('High Performance MySQL +author_id_123' IN BOOLEAN MODE);
这个案例中,如果author列的选择性非常高,那么MySQL能够根据作者信息很快地将需要过滤的文档记录限制在一个很小的范围内,这个查询的效率也就会非常耗。如果author列的选择性很低,那么这个替代方案的效率会比前面那个更糟,所以使用的时候要谨慎。全文索引有时候还可以实现一些简单的"边框"搜索。例如,希望搜索某个坐标范围时,将坐标按某种方式转换成文本再进行全文搜索。假设某条记录的坐标为X=123和Y=456.可以按照这样的方式交错存储坐标XY123456,然后对此进行全文搜索。这时,希望查询某矩形——X取值100至199,Y取值400至499——范围时,可以再查询直接搜索"+XY14*".这比使用WHERE条件过滤的效率要高很多。全文索引的另一个常用技巧时缓存全文索引返回的主键值,这在分页显示的时候经常使用。当应用程序真的需要输出结果时,才通过主键值将所有需要的数据返回。这个查询就可以自由地使用其他索引、或者自由地关联其他表。在早期版本中虽然只有MyISMA表支持全文索引,但是如果仍然希望使用InnoDB或其他引擎,可以将原表赋值到一个备库,再将备库上的表改成MyISAM并建上相应的全文索引。如果不希望再另一个服务器上完成查询,还可以对表进行垂直拆分,将需要索引的列放到一个单独的MyISAM表中。将需要索引的列额外地冗余再另一个MyISAM表中也是一个办法。再测试库sakila.film_text就是使用这个策略,这里使用触发器来维护这个表的数据。最后,你还可以使用一个包含内置全文索引的引擎,,如Lucene或者Sphinx。因为使用全文索引的时候,通常会返回大量结果并产生大量随机IO,如何和GROUP BY 一起使用的话,还需要通过临时表或者文件排序进行分组,性能会非常非常糟糕。这类查询通常只是希望查询分组后的前几名结果,所以一个有效的优化办法是对结果进行抽样而不是精确计算。例如,仅查询前面的
1 000条记录,进行分组并返回前几名的结果。
全文索引的配置和优化
全文索引的日常维护通常能够大大提升性能。"双B-Tree"的特殊结构、再某些文档中比其他文档要包含多得多的关键字,这都使得全文索引比起普通索引有更多的碎片问题,所以需要经常使用OPTIMIZE TABLE来减少碎片。如果应用时IO密集型的,那么定期地进行全文索引重建可以让性能提升很多。如果希望全文索引能够高效地工作,还需要保证索引缓存足够大,从而保证所有的全文索引都能够缓存在内存中。通常,可以为全文索引设置单独的键缓存(Key Cache),保证不会被其他的索引缓存挤出内存。
提供一个好的停用词表也很重要。默认的停用词表对常用英语来说可能还不错,但是如果时其他语言或者某些专业文档就不合适了,例如技术文档。例如,若要索引一批MySQL相关的文档,那么最好将mysql放入停用词表,因为在这类文档中,这个词会出现得非常频繁。忽略一些太短的单词也可以提升全文索引的效率。索引单词的最小长度可以通过参数ft_min_word_len配置。修改该参数可以过滤更多的单词,让查询速度更快,但是也会降低精确度。还需要注意一些特殊的而场景,有时确实需要索引某些非常短的词语。例如,对一个电子消费品文档进行索引,除非我们允许对很短的单词进行索引,否则搜索
"cd player"可能会返回大量的结果。因为单词"cd"比默认允许的最短长度4还要小,所以这里只会对"Player"进行搜索,而通常搜索"cd player"的客户,其实对MP3或者DVD播放器并不感兴趣。停用词表和最小词长都可以通过减少索引词语来提升全文索引的效率,但是同时地也会降低搜索的精确度。这需要根据实际的应用场景找到合适的平衡点。如果你希望同时获得好的性能和好的搜索质量,那么需要自己定制这些参数。一个好的办法时通过日志系统来研究用户的搜索行为,看看一些异常的查询,包括没有结果返回的查询或者返回过多结果的用户查询。通过这些用户行为和被搜索的内容来判断应该如何调整索引策略。
(需要注意,当调整"允许最小词长"后,需要通过OPTIMIZE TABLE来重建索引才会生效。另一个参数ft_max_word_len和该参数行为类似,它限制了允许索引的最大词长)。
当向一个有全文索引的表中导入大量数据的时候,最好先通过命令DISABLE KEYS来禁用全文索引,然后再导入结束后使用ENABLE KEYS来建立全文索引。因为全文索引的更新是一个消耗很大的操作,所以上面的细节会帮你节省大量时间。另外,这样还顺便为全文索引做了一次碎片整理工作。如果数据集特别大,则需要对数据进行手动分区,然后将数据分布到不同的节点,再做并行的搜索。这是一个复杂的工作,最好通过一些外部的搜索引擎来实现,如Lucene或者Sphinx,经验显示这样做性能会有指数级的提升