登录 用户中心() [退出] 后台管理 注册
   
您的位置: 首页 >> 程序员学前班[不再更新,只读] >> 主题: mysql 全文检索     [回主站]     [分站链接]
标题
mysql 全文检索
clq
浏览(0) + 2010-01-29 17:08:06 发表 编辑

关键字:

mysql 全文检索
clq
2010-1-29 17:09:00 发表 编辑

http://info.codepub.com/2006/10/info-11097.html
--------------------------------------------------

通过MySQL内置全文检索实现中文的相关检索

  • 阅览次数: 今天:0 总浏览:3392
  • 文章来源: CP整理
  • 原文作者: walker
  • 整理日期: 2007-02-07
  • 发表评论

关键字:MySQL 全文检索 全文索引 中文分词 二元分词 区位码 相似度

/**
* @author : walkerlee
* @copyright : www.neatstudio.com | www.walkerlee.net
*/

转载请保留以上信息。

注:本文使用的MySQL版本为:MySQL 4.0.x

在MySQL4中,是已经开始支持全文检索(索引)的了。但是只是对英文支持全文检索。
由于英文在书写上的特殊性,使得分词算法相对中文来说,简单得多。一般来说,我们可以通过单词与单词之间的空格,以及标点符号来完成这个分词过程。
但是就中文来说,就没有那么简单。MySQL无法对中文做出正确的分词,假设有如下英文句子:

 

"Hello world! Hello PHP!"

通过上面提及的方法,可以很简单的把这个句子分词为:

1 Hello
2 world
3 PHP

我们再来看看中文的句子:

 "你好世界,你好PHP!"

按照英文的算法,分词如下:

1 你好世界
2 你好PHP

显然是不能满足我们的需要的。

所以,首先我们要做的是,把中文的句子转变为MySQL眼中的英文,以便使得它能以英文分词算法去对句子进行正确的分词处理。
先将上面中文句子进行标点过滤处理,得到以下句子:

 你好世界 你好PHP

接着再使用中文分词中较简单实现的二元分词算法对句子进行二元分词,得到以下句子:

 你好 好世 世界 你好 PHP

因为把标点符号替换为空格,以及PHP本身为英文字母的关系,可以不用进行二元切分,所以得到上面句子。
这个时候,我们来看看处理过后的句子,会发现,就其书写格式上来说,已经符合英文的书写格式,既以空格,标点来对单词形成自然间隔。只是上面句子没有标点,只有空格而已。
到此,我们已经成功的将中文“翻译”为MySQL能理解的“英文”书写格式。

但是,问题还没解决,首先,MySQL中,ft_min_word_len(分词词汇最小长度)这个参数的默认值为4,也就是4个字母以上长度的单词,才会被考虑,小于4个的,将会被忽略。
如果不改变这个长度,按照上面的分词结果,我们将无法通过 你好,世界,PHP等检索到相关的结果,因为分出来的词太短了,不在MySQL的选择范围内。
我们可以通过修改ft_min_word_len的值,将其设置为2来解决上面问题,但是这样做的话,在检索列表中的原本就为英文的短小词汇,如:PHP,MP3,也会被划入检索范围内,这样做的结果是,出现很多无意义的相关结果。

请看以下列表:

 [MP3] the look
[MP3] because of you

因为他们都同有MP3在标题中,所以会出现上述提到的问题。

回到ft_min_word_len值的问题,我们之所以要修改他,是为了能让MySQL找到我们的二元分 词,但是短小的英文又被“无辜”的卷入,我们目前要解决的问题就是,如何使得MySQL能检索到二个字的中文词汇,又能忽略掉原本的英数?第一个反应是把 中文MD5,这样以上分词就将转化为以下结果:

 你 好 好世 世界 你好 PHP => b94ae3c6d892b29cf48d9bea819b27b9 f5625345be46432fb0fd51340fcf6679 9067de5206278a93823f9c5dc2c737fd b94ae3c6d892b29cf48d9bea819b27b9 PHP

这样做,首先是使得中文分词的长度超越了默认的2个字,同时消除了中文的歧义性。(MySQL4对中文的处理有问题),搜索“车轮”时候,不再会出现类似“发动机”结果的问题。(车轮的例子只是为了方便理解而做出的假设)

通过上面的做法,已经解决了分词最小长度的问题,顺利的把中文词汇长度升级,从而达到把中文词汇划入检索范围,把较短的英数划出检索范围。
休息一下,然后发现这个MD5后的字符串是否太长了点……比较占用空间,要不,于是想到区位码,4位数的区位码能表示一个GB汉字,一个词有二个汉字组成,转换为区位码后是8个数字。不但能确定惟一性,也就MD5而已减少了长度。下面是转换后的:

 你 好 好世 世界 你好 PHP => b94ae3c6d892b29cf48d9bea819b27b9 f5625345be46432fb0fd51340fcf6679 9067de5206278a93823f9c5dc2c737fd b94ae3c6d892b29cf48d9bea819b27b9 PHP => 36672635 26354232 42322971 36672635 PHP

呵呵,是不是比MD5的小了很多呢?最后我们把相同的词汇留一个,多余的删除。得到

 36672635 26354232 42322971 PHP

于是就完成了 "你好世界,你好PHP!" 到 "36672635 26354232 42322971 PHP" 的转换。

通过上面方法结合MySQL全文检索语句,我们可以通过给出一个标题例如:"迈克尔·杰克逊 -《危险之旅之布加勒斯特站》"找出类似以下的相关标题

迈克尔杰克逊 -《迈克尔杰克逊危险布加勒斯特演唱会》
Michael Jackson -《迈克尔杰克逊 罗马尼亚 危险演唱会》
迈克尔杰克Michael Jackson -《危险之旅》
迈克尔杰克逊 -《迈克尔杰克逊 美国50annive演唱会危险片段》
迈克尔杰克逊 -《迈克尔杰克逊 终极收藏 原版DVD危险演唱会》
迈克尔杰克逊  杰克逊五兄弟 -《The Jackson Motown 25 演唱会》
迈克尔杰克逊 -《迈克尔杰克逊BAD曰本Yokohama演唱会》
迈克尔杰克逊 -《迈克尔杰克逊曰本大阪演唱会》
迈克尔杰克逊 -《迈克尔杰克逊之胜利-达拉丝演唱会》
迈克尔杰克逊 -《迈克尔杰克逊之胜利演唱会 比丽珍 片段》
迈克尔杰克逊 -《迈克尔杰克逊德国危险演唱会之 billie jean片段》
迈克尔杰克逊 -《Michael Jackson -30周年演唱会》
Michael Jackson -《迈克尔杰克逊 马尼拉 历史演唱会》
迈克尔杰克逊 -《1993年美国橄榄球中场休息精彩表演》

表结构 article
title  varchar 200    --------   用于存放标题 (显示用)
ft      text     ----    fulltext  用于存放标题分词结果 (检索用)

首先我们在把标题保存到数据库时候,就已经对标题进行分词转区位码,保存到ft字段中,用于相关性的检索。
然后把给出的标题"迈克尔·杰克逊 -《危险之旅之布加勒斯特站》"转为"34853143 31432291 22910104 01042960 29603143 31434923 46034753 47535414 54143435 34355414 54141828 18282851 28513253 32534325 43254456 44565330",最后进行全文检索查询:

 SELECT title, MATCH( ft ) AGAINST( '34853143 31432291 22910104 01042960 29603143 31434923 46034753 47535414 54143435 34355414 54141828 18282851 28513253 32534325 43254456 44565330' IN BOOLEAN MODE ) AS score
FROM article
WHERE MATCH( ft ) AGAINST( '34853143 31432291 22910104 01042960 29603143 31434923 46034753 47535414 54143435 34355414 54141828 18282851 28513253 32534325 43254456 44565330' IN BOOLEAN MODE )
ORDER BY score DESC
LIMIT 0, 5

从SQL Query上来看,进行了两次全文检索,其实不然,MySQL会将其视为一次,所以不比担心。
同时使用了AS score,这个score是相似度,分值越高,自然越与给出的标题相近。

二点建议:
1.在实际使用中,挑选score大于1的作为检索结果。
2.检索结果会将本身标题也算入其中,根据score排序,为第一条,别忘记过滤哦 ^_^。

站在用户的立场来说,我们给用户提供了更多的相关内容,站在搜索引擎立场上来说,给关键字提供了更多的相关链接,形成了良好的站内互联结构,提高了搜索引擎对网页的评价。

如果各位碰到错误的不合理的地方,恳请指正,共同进步。谢谢!

参考资料:

1.Monkey的二元分词 作者:Monkey http://info.codepub.com/2006/10/info-10732.html
2.PHP里如何实现汉字转区位码 提供者:haoyoul http://info.codepub.com/2006/10/info-9502.html

3.对dvbbs.php全文搜索的完全分析 作者:fcicq  http://info.codepub.com/2006/10/info-10201.html



clq
2010-1-29 17:09:53 发表 编辑

参考 "两个东亚文字之间都留上空格"

昨天有访客提到网志程序senrendipity的站内搜索功能(需要用到MySQL数据库的全文检索技术)对中文的支持很差。我当时的回复是“限于客观原因等,此问题暂无理想的方案”。

后来我进一步地去网上做了相关的调查。根据MySQL数据库产品开发人员的意见,MySQL全文检索技术对东亚文字(包括中文、日文和韩文)的支持很差,是因为对于东亚文字的有效分词很难实现。一个并不完美但可以参考的使用方案是在用数据库存储东亚文字的时候,将两个东亚文字之间都留上空格,这样就可以实现对东亚文字的全文检索了。

当然这个方案只是一个临时的无可奈何之举。

2个著名的免费数据库系统MySQL和PostgreSQL都支持全文检索,但好像都不支持东亚文字的全文检索。而据我所知微软的SQL Server数据库产品是支持针对中文的全文检索的(想必Oracle也应该支持中文的全文检索)。因此,我在想,为什么就没有人去做一下MySQL的中 文全文检索的研究工作呢?我们很多的研究生所作的毕业论文涉及各种各样的课题,但其中真正有价值、有意义的也许不是很多,那为什么不能把我们有限的精力拿 来做点更有意义的事情呢?

当然我是不会去做这方面的研究的了,但我期望有人能够去解决这个问题。

clq
2010-2-1 14:12:08 发表 编辑

SELECT * FROM table1

 WHERE MATCH (field1) AGAINST ('key1'); ft_min_word_len

clq
2010-2-1 15:30:31 发表 编辑

" A phrase that is enclosed within double quote (“"”) characters matches only rows that contain the phrase literally, as it was typed. 
双引号能精确匹配,但是它里面却不能用通配符.比如:WOOD43 , WOOD66 这些,用户如果只输入 WOOD 是找不到这些数据的.
--------------------------------------------------

"some words"
... 可以包含 “some words of wisdom”,但不是 “some noise words”
--------------------------------------------------
SELECT *,MATCH (m1) AGAINST ('"错 不"' IN BOOLEAN MODE  ) FROM f1

 WHERE MATCH (m1) AGAINST ('"错 不 很"' IN BOOLEAN MODE );
--------------------------------------------------
将文字按单个中文切割开来,然后加双引号就是我所需要的。(是目前我所需要的)数据库中的要搜索的内容都如此处理。
--------------------------------------------------
CREATE TABLE `f1` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `m1` varchar(255) DEFAULT NULL,
  `t1` longtext,
  PRIMARY KEY (`Id`),
  FULLTEXT KEY `f_i1` (`m1`)
) ENGINE=MyISAM AUTO_INCREMENT=18 DEFAULT CHARSET=gbk;
INSERT INTO `f1` VALUES (1,'1111','1111');
INSERT INTO `f1` VALUES (2,'中文 ',NULL);
INSERT INTO `f1` VALUES (3,'中文2 ',NULL);
INSERT INTO `f1` VALUES (4,'中文3',NULL);
INSERT INTO `f1` VALUES (5,'中文4',NULL);
INSERT INTO `f1` VALUES (6,'中文11',NULL);
INSERT INTO `f1` VALUES (7,'中文',NULL);
INSERT INTO `f1` VALUES (8,'中文123',NULL);
INSERT INTO `f1` VALUES (9,'中文123中文123',NULL);
INSERT INTO `f1` VALUES (10,'中文中文123',NULL);
INSERT INTO `f1` VALUES (11,'aaaaaa aaaaaa',NULL);
INSERT INTO `f1` VALUES (12,'select a * from',NULL);
INSERT INTO `f1` VALUES (13,'中文',NULL);
INSERT INTO `f1` VALUES (14,'很 不 错 ',NULL);
INSERT INTO `f1` VALUES (15,'中文很不错',NULL);
INSERT INTO `f1` VALUES (16,'错 不 很',NULL);
INSERT INTO `f1` VALUES (17,'错不 不很 ',NULL);
--------------------------------------------------
my.ini 中 ft_min_word_len=1 , 听说 ft_min_word_len=2 也可以,或者对单个字符作增加字符处理。效率上 ft_min_word_len=2 可能好点.


clq
2010-2-1 15:32:47 发表 编辑



全文索引在 MySQL 中是一个FULLTEXT类型索引。FULLTEXT索引用于MyISAM表,可以在CREATE TABLE时或之后使用ALTER TABLE或CREATE INDEX在CHAR、VARCHAR或TEXT列上创建。对于大的数据库,将数据装载到一个没有FULLTEXT索引的表中,然后再使用ALTER TABLE(或CREATE INDEX) 创建索引,这将是非常快的。将数据装载到一个已经有FULLTEXT索引的表中,将是非常慢的。

全文搜索通过MATCH()函数完成。

mysql> CREATE TABLE articles (
    ->   id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
    ->   title VARCHAR(200),
    ->   body TEXT,
    ->   FULLTEXT (title,body)
    -> );
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO articles VALUES
    -> (NULL,'MySQL Tutorial', 'DBMS stands for DataBase ...'),
    -> (NULL,'How To Use MySQL Efficiently', 'After you went through a ...'),
    -> (NULL,'Optimising MySQL','In this tutorial we will show ...'),
    -> (NULL,'1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'),
    -> (NULL,'MySQL vs. YourSQL', 'In the following database comparison ...'),
    -> (NULL,'MySQL Security', 'When configured properly, MySQL ...');
Query OK, 6 rows affected (0.00 sec)
Records: 6  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM articles
    ->          WHERE MATCH (title,body) AGAINST ('database');
+----+-------------------+------------------------------------------+
| id | title             | body                                     |
+----+-------------------+------------------------------------------+
|  5 | MySQL vs. YourSQL | In the following database comparison ... |
|  1 | MySQL Tutorial    | DBMS stands for DataBase ...             |
+----+-------------------+------------------------------------------+
2 rows in set (0.00 sec)

函数MATCH()对照一个文本集(包含在一个FULLTEXT索引中的一个或多个列的列集)执行一个自然语言搜索一个字符串。搜索字符串做为AGAINST()的参数被给定。搜索以忽略字母大小写的方式执行。对于表中的每个记录行,MATCH()返回一个相关性值。即,在搜索字符串与记录行在MATCH()列表中指定的列的文本之间的相似性尺度。

当MATCH()被使用在一个WHERE子句中时 (参看上面的例子),返回的记录行被自动地以相关性从高到底的次序排序。相关性值是非负的浮点数字。零相关性意味着不相似。相关性的计算是基于:词在记录行中的数目、在行中唯一词的数目、在集中词的全部数目和包含一个特殊词的文档(记录行)的数目。

它也可以执行一个逻辑模式的搜索。这在下面的章节中被描述。

前面的例子是函数MATCH()使用上的一些基本说明。记录行以相似性递减的顺序返回。

下一个示例显示如何检索一个明确的相似性值。如果即没有WHERE也没有ORDER BY子句,返回行是不排序的。

mysql> SELECT id,MATCH (title,body) AGAINST ('Tutorial') FROM articles;
+----+-----------------------------------------+
| id | MATCH (title,body) AGAINST ('Tutorial') |
+----+-----------------------------------------+
|  1 |                        0.64840710366884 |
|  2 |                                       0 |
|  3 |                        0.66266459031789 |
|  4 |                                       0 |
|  5 |                                       0 |
|  6 |                                       0 |
+----+-----------------------------------------+
6 rows in set (0.00 sec)

下面的示例更复杂一点。查询返回相似性并依然以相似度递减的次序返回记录行。为了完成这个结果,你应该指定MATCH()两次。这不会引起附加的开销,因为 MySQL 优化器会注意到两次同样的MATCH()调用,并只调用一次全文搜索代码。

mysql> SELECT id, body, MATCH (title,body) AGAINST
    -> ('Security implications of running MySQL as root') AS score
    -> FROM articles WHERE MATCH (title,body) AGAINST
    -> ('Security implications of running MySQL as root');
+----+-------------------------------------+-----------------+
| id | body                                | score           |
+----+-------------------------------------+-----------------+
|  4 | 1. Never run mysqld as root. 2. ... | 1.5055546709332 |
|  6 | When configured properly, MySQL ... |   1.31140957288 |
+----+-------------------------------------+-----------------+
2 rows in set (0.00 sec)

MySQL 使用一个非常简单的剖析器来将文本分隔成词。一个“词”是由文字、数据、“'”和“_”组成的任何字符序列。任何在 stopword 列表上出现的,或太短的(3 个字符或更少的)的 “word” 将被忽略。

在集和查询中的每个合适的词根据其在集与查询中的重要性衡量。这样,一个出现在多个文档中的词将有较低的权重(可能甚至有一个零权重),因为在这个特定的集中,它有较低的语义值。否则,如果词是较少的,它将得到一个较高的权重。然后,词的权重将被结合用于计算记录行的相似性。

这样一个技术工作可很好地工作与大的集(实际上,它会小心地与之谐调)。 对于非常小的表,词分类不足以充份地反应它们的语义值,有时这个模式可能产生奇怪的结果。

mysql> SELECT * FROM articles WHERE MATCH (title,body) AGAINST ('MySQL');
Empty set (0.00 sec)

在上面的例子中,搜索词MySQL却没有得到任何结果,因为这个词在超过一半的记录行中出现。同样的,它被有效地处理为一个 stopword (即,一个零语义值的词)。这是最理想的行为 -- 一个自然语言的查询不应该从一个 1GB 的表中返回每个次行(second row)。

匹配表中一半记录行的词很少可能找到相关文档。实际上,它可能会发现许多不相关的文档。我们都知道,当我们在互联网上通过搜索引擎试图搜索某些东西时,这会经常发生。因为这个原因,在这个特殊的数据集中,这样的行被设置一个低的语义值。

到 4.0.1 时,MySQL 也可以使用IN BOOLEAN MODE修饰语来执行一个逻辑全文搜索。

mysql> SELECT * FROM articles WHERE MATCH (title,body)
    ->     AGAINST ('+MySQL -YourSQL' IN BOOLEAN MODE);
+----+------------------------------+-------------------------------------+
| id | title                        | body                                |
+----+------------------------------+-------------------------------------+
|  1 | MySQL Tutorial               | DBMS stands for DataBase ...        |
|  2 | How To Use MySQL Efficiently | After you went through a ...        |
|  3 | Optimising MySQL             | In this tutorial we will show ...   |
|  4 | 1001 MySQL Tricks            | 1. Never run mysqld as root. 2. ... |
|  6 | MySQL Security               | When configured properly, MySQL ... |
+----+------------------------------+-------------------------------------+

这个查询返回所有包含词MySQL的记录行(注意: 50% 的阈值没有使用),但是它没有包含词YourSQL。注意,一个逻辑模式的搜索不会自动地以相似值的降序排序记录行。你可以从上面的结果出看得出来,最高的相似值(包含MySQL两次的那个) 最列在最后,而不是第一位。一个逻辑全文搜索即使在没有一个FULLTEXT索引的情况下也可以工作,然而它慢些。

逻辑全文搜索支持下面的操作符:

+
    一个领头的加号表示,该词必须出现在每个返回的记录行中。

-
    一个领头的减号表示,该词必须不出现在每个返回的记录行中。

    缺省的 (当既没有加号也没有负号被指定时)词是随意的,但是包含它的记录行将被排列地更高一点。这个模仿没有IN BOOLEAN MODE修饰词的MATCH() ... AGAINST()的行为。

< >
    这两个操作符用于改变一个词的相似性值的基值。<操作符减少基值,>操作符则增加它。参看下面的示例。

( )
    圆括号用于对子表达式中的词分组。

~
    一个领头的否定号的作用象一个否定操作符,引起行相似性的词的基值为负的。它对标记一个噪声词很有用。一个包含这样的词的记录将被排列得低一点,但是不会被完全的排除,因为这样可以使用-操作符。

*
    一个星号是截断操作符。不想其它的操作符,它应该被追加到一个词后,不加在前面。

"
    短语,被包围在双引号"中,只匹配包含这个短语(字面上的,就好像被键入的)的记录行。

这里是一些示例:

apple banana
    找至少包含上面词中的一个的记录行
+apple +juice
    ... 两个词均在被包含
+apple macintosh
    ... 包含词 “apple”,但是如果同时包含 “macintosh”,它的排列将更高一些
+apple -macintosh
    ... 包含 “apple” 但不包含 “macintosh”
+apple +(>pie <strudel)
    ... 包含 “apple” 和 “pie”,或者包含的是 “apple” 和 “strudel” (以任何次序),但是 “apple pie” 排列得比 “apple strudel” 要高一点
apple*
    ... 包含 “apple”,“apples”,“applesauce” 和 “applet”
"some words"
    ... 可以包含 “some words of wisdom”,但不是 “some noise words”

 全文的限制

    * MATCH()函数的所有参数必须是从来自于同一张表的列,同时必须是同一个FULLTEXT索引中的一部分,除非MATCH()是IN BOOLEAN MODE的。

    * MATCH()列列表必须确切地匹配表的某一FULLTEXT索引中定义的列列表,除非MATCH()是IN BOOLEAN MODE的。

    * AGAINST()的参数必须是一个常量字符串。

微调 MySQL 全文搜索

不幸地,全文搜索仍然只有很少的用户可调参数,虽然增加一些在 TODO 上排列很高。如果你有一个 MySQL 源码发行,你可以发挥对全文搜索的更多控制。

注意,全文搜索为最佳的搜索效果,被仔细地调整了。修改默认值的行为,在大多数情况下,只会使搜索结果更糟。不要修改 MySQL 的源代码,除非你知道你在做什么!

    * 被索引的词的最小长度由 MySQL 变量ft_min_word_len指定。

    * stopword 列表可以从ft_stopword_file变量指定的文件中读取。

    * 50% 阈值选择由所选择的特殊的衡量模式确定。为了禁止它,修改`myisam/ftdefs.h'文件中下面的一行:

      #define GWS_IN_USE GWS_PROB

      改为:

      #define GWS_IN_USE GWS_FREQ

      然后重新编译 MySQL。在这种情况下,不需要重建索引。注意:使用了这个,将严重地减少 MySQL 为MATCH()提供足够的相似性值的能力。如果你确实需要搜索这样的公共词,最好使用IN BOOLEAN MODE的搜索代替,它不遵守 50% 的阈值。

    * 有时,搜索引擎维护员希望更改使用于逻辑全文搜索的操作符。这些由变量ft_boolean_syntax定义。对于这些更改,要求你重建你的FULLTEXT索引,对于一个 MyISAM 表,最容易的重建索引文件的方式如下面的语句:

mysql> REPAIR TABLE tbl_name QUICK;

全文搜索 TODO

    * 使所有对FULLTEXT索引的操作更快
    * 邻近(Proximity)操作符
    * 对 "always-index words" 的支持。他们可以是用户希望视为一个词处理的任意字符串,例如 "C++"、"AS/400"、"TCP/IP",等等
    * 支持在MERGE表中的全文搜索
    * 对多字节字符的支持
    * 依照数据的语言建立 stopword 列表
    * Stemming (当然,依赖于数据的语言)
    * Generic user-suppliable UDF preparser.
    * 使模式更加灵活 (通过为CREATE/ALTER TABLE中的FULLTEXT增加某些可调整参数)


clq
2010-10-2 10:47:55 发表 编辑

http://www.searchdatabase.com.cn/showcontent_23558.htm
这是一个未经证实的方法.
--------------------------------------------------
MySQL全文检索中Like索引的实现
2009-8-18    来源:51cto   作者:蓝皮鼠

导读:在数据库使用中,DBA都会告诉大家SQL的LIKE条件为%XXX%号时,由于不能使用索引,当数据量变大时(比如超过百万条),全表扫描会导致性能很差。

关键词:MySQL DBA 全文索引
正在加载数据...

  在数据库使用中,DBA都会告诉大家SQL的LIKE条件为%XXX%号时,由于不能使用索引,当数据量变大时(比如超过百万条),全表扫描会导致性能很差。

  但是在实际业务中,很难避免MySQL全文检索并Like索引的这种需求。比如模糊搜索用户帐号,昵称之类。既然这个需求必须做,但又不可以直接用 LIKE。这里我和大家分享一下我们关于这种需求的一种解决方案。当然别人也可能采用过类似的办法,我不是很清楚。所以也用一下“原创”吧。

  MySQL数据库很早就支持全文索引,但是全文索引和LIKE语句是不同的。具体点说,全文索引的单位是词,耳LIKE匹配的是字符。当然实际的区别更大,比如“老鼠爱大米”这段文本用全文搜索的话,条件“老鼠爱大米”,“老鼠和大米”,“大米老鼠”,“大米与老鼠”会搜索到内容,但是“爱”,“鼠爱”,“爱大”不会搜索到内容。反之,使用LIKE搜索时,“老鼠和大米”,“大米老鼠”,“大米与老鼠”不会找到内容,而“爱”,“鼠爱”,“爱大”会找到内容。我们这里不讨论两种方式的优劣,根据实际情况每种功能都会有各自的实际需求。比如对于大段文本,全文检索是最好的方法,但是对于姓名,帐号,昵称等很短的通常无意义文本,LIKE会更合适一些。

  虽然全文检索和LIKE搜索不同,但是在特殊情况下,可以用全文搜索功能来实现LIKE搜索。具体就是每个字符作为一个词,而且使用双引号来限制词精确匹配(简单点说就是老鼠大米和大米老鼠不同),这样可以实现LIKE搜索的功能。

  下面还是说一下具体的做法吧。

  首先,数据库指定 --ft_min_word_len=2 --ft_stopword_file=""。第一个参数是告诉数据库,小于2个字符的词忽略。第二个是告诉数据库不忽略任何特殊词。这些设置是给实现功能创造条件。

  然后建搜索表

  CREATE TABLE tbl_search (
  id int(10) unsigned NOT NULL auto_increment,
  name varchar(500),
  PRIMARY KEY (id),
  FULLTEXT KEY idx_name (name)
  ) ENGINE=MyISAM AUTO_INCREMENT=1;
  static String encode(String input) {
  if (input == null) return null;
  StringBuilder output = new StringBuilder();
  for (int i = 0, c = input.length(); i < c; ++i) {
  char ch = input.charAt(i);
  if (ch >= '0' && ch <= '9' || ch >= 'A' && ch <= 'Z'
  || ch >= '0' && ch <= '9' || ch >= 'A' && ch <= 'Z'
  || ch == '_' || ch == '-') {
  output.append(Integer.toHexString(ch)).append(' ');
  } else if (ch >= 'a' && ch <= 'z' || ch >= 'a' && ch <= 'z') {
  output.append(Integer.toHexString((int)ch - 32)).append(' ');
  } else {
  Character.UnicodeBlock block = Character.UnicodeBlock.of(ch);
  if (block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
  || block == Character.UnicodeBlock.KATAKANA
  || block == Character.UnicodeBlock.HIRAGANA) {
  output.append(Integer.toHexString(ch)).append(' ');
  } else {
  // do nothing
  }
  }
  }
  // trim blank
  int last = output.length() - 1;
  if (last > 0 && output.charAt(last) == ' ') {
  output.deleteCharAt(last);
  }
  return output.toString();
  }

  使用上面的代码对要搜索的内容编码,比如内容是“蓝皮鼠2008”,编码后的结果是“84dd 76ae 9f20 32 30 30 38”。将编码后的内容存入name字段。

  使用如下SQL语句进行搜索

  select * from tbl_search where match(name) against('"76ae 9f20 32"' in boolean mode)
  这样就基本实现了MySQL全文检索中的Like索引。

clq
2010-10-3 9:21:50 发表 编辑

in boolean mode 是一定要有的,说明见
http://dev.mysql.com/doc/refman/5.1/zh/functions.html#fulltext-boolean

但加了双引号的模式虽然可以模仿 like 但是性能上似乎连 like 都比不上. 用 +keyword 的方式效果差一点,但似乎性能上好很多?


总数:7 页次:1/1 首页 尾页  
总数:7 页次:1/1 首页 尾页  


所在合集/目录



发表评论:
文本/html模式切换 插入图片 文本/html模式切换


附件:



NEWBT官方QQ群1: 276678893
可求档连环画,漫画;询问文本处理大师等软件使用技巧;求档softhub软件下载及使用技巧.
但不可"开车",严禁国家敏感话题,不可求档涉及版权的文档软件.
验证问题说明申请入群原因即可.

Copyright © 2005-2020 clq, All Rights Reserved
版权所有
桂ICP备15002303号-1