PostgreSQL全文检索zhparser使用

摘要:
我在网上查到,PG有两个开源中文分词插件:nlpbamboo和zhparser。以下是使用zhparser进行中文全文检索的尝试。Zhparser是基于SimpleChineseWordSegmentation中文分词数据库的PG扩展。作者是amutu,源URL是https://github.com/amutu/zhparser 。

本文引用自: http://blog.chinaunix.net/uid-20726500-id-4820580.html

防止文章丢失才进行复制

PostgreSQL支持全文检索,其内置的缺省的分词解析器采用空格分词。因为中文的词语之间没有空格分割,所以这种方法并不适用于中文。要支持中文的全文检索需要额外的中文分词插件。网上查了下,可以给PG用的开源中文分词插件有两个:nlpbamboo和zhparser。但是nlpbamboo是托管在googlecode上的,而googlecode被封了,下载不方便。下面尝试采用zhparser进行中文的全文检索。

zhparser是基于Simple Chinese Word Segmentation(SCWS)中文分词库实现的一个PG扩展,作者是 amutu,源码URL为https://github.com/amutu/zhparser。

1. 安装

1.1 下载SCWS

http://www.xunsearch.com/scws/down/scws-1.2.2.tar.bz2

1.2 编译和安装SCWS

tar xvf scws-1.2.2.tar.bz2
cd scws-1.2.2
./configure
make install

1.3 下载zhparser

https://github.com/amutu/zhparser/archive/master.zip

1.4 编译和安装zhparser

确保PostgreSQL的二进制命令路径在PATH下,然后解压并进入zhparser目录后,编译安装zhparser。
SCWS_HOME=/usr/local make && make install

2 配置中文全文检索

连接到目标数据库进行中文全文检索的配置

2.1 安装zhparser扩展

点击(此处)折叠或打开

  1. -bash-4.1$ psql testdb
  2. psql (9.4.0)
  3. Type "help" for help.
  4. testdb=# create extension zhparser;
  5. CREATE EXTENSION


安装zhparser扩展后多一个叫“zhparser”的解析器

点击(此处)折叠或打开

  1. testdb=# dFp
  2.          List of text search parsers
  3.    Schema | Name | Description 
  4. ------------+----------+---------------------
  5.  pg_catalog | default | default word parser
  6.  public | zhparser | 
  7. (2 rows)


zhparser可以将中文切分成下面26种token
点击(此处)折叠或打开

  1. testdb=# select ts_token_type('zhparser');
  2.               ts_token_type 
  3. -----------------------------------------
  4.  (97,a,adjective)
  5.  (98,b,"differentiation (qu bie)")
  6.  (99,c,conjunction)
  7.  (100,d,adverb)
  8.  (101,e,exclamation)
  9.  (102,f,"position (fang wei)")
  10.  (103,g,"root (ci gen)")
  11.  (104,h,head)
  12.  (105,i,idiom)
  13.  (106,j,"abbreviation (jian lue)")
  14.  (107,k,head)
  15.  (108,l,"tmp (lin shi)")
  16.  (109,m,numeral)
  17.  (110,n,noun)
  18.  (111,o,onomatopoeia)
  19.  (112,p,prepositional)
  20.  (113,q,quantity)
  21.  (114,r,pronoun)
  22.  (115,s,space)
  23.  (116,t,time)
  24.  (117,u,auxiliary)
  25.  (118,v,verb)
  26.  (119,w,"punctuation (qi ta biao dian)")
  27.  (120,x,unknown)
  28.  (121,y,"modal (yu qi)")
  29.  (122,z,"status (zhuang tai)")
  30. (26 rows)


2.2 创建使用zhparser作为解析器的全文搜索的配置

点击(此处)折叠或打开

  1. testdb=# CREATE TEXT SEARCH CONFIGURATION testzhcfg (PARSER = zhparser);
  2. CREATE TEXT SEARCH CONFIGURATION

2.3 往全文搜索配置中增加token映射

点击(此处)折叠或打开

  1. testdb=# ALTER TEXT SEARCH CONFIGURATION testzhcfg ADD MAPPING FOR n,v,a,i,e,l WITH simple;
  2. ALTER TEXT SEARCH CONFIGURATION

上面的token映射只映射了名词(n),动词(v),形容词(a),成语(i),叹词(e)和习用语(l)6种,这6种以外的token全部被屏蔽。词典使用的是内置的simple词典,即仅做小写转换。根据需要可以灵活定义词典和token映射,以实现屏蔽词和同义词归并等功能。

3.中文分词测试

点击(此处)折叠或打开

  1. testdb=# select to_tsvector('testzhcfg','南京市长江大桥');
  2.        to_tsvector 
  3. -------------------------
  4.  '南京市':1 '长江大桥':2
  5. (1 row)


中文分词有最大匹配,最细粒度等各种常用算法。上面的分词结果没有把'长江大桥'拆成'长江'和'大桥'两个词,所以SCWS估计是采取的最大匹配的分词算法。
分词算法的优劣一般通过3个指标衡量。
效率:
  索引和查询的效率
召回率:
  提取出的正确信息条数 /  样本中的信息条数 
准确率:
  提取出的正确信息条数 /  提取出的信息条数
分词的粒度越粗,效率越高,但遗漏的可能性也会高一点,即召回率受影响。具体到上面的例子,用'南京&大桥'就没法匹配到。

点击(此处)折叠或打开

  1. testdb=# select to_tsvector('testzhcfg','南京市长江大桥') @@ '南京&大桥';
  2.  ?column? 
  3. ----------
  4.  f
  5. (1 row)


效率,召回率和准确率3个指标往往不能兼顾,所以不能笼统的说最大匹配好还是不好。但是如果特别在乎召回率,SCWS也提供了一些选项进行调节。下面是scws命令可接受的参数。
http://www.xunsearch.com/scws/docs.php#utilscws

点击(此处)折叠或打开

  1. 1. **$prefix/bin/scws** 这是分词的命令行工具,执行 scws -h 可以看到详细帮助说明。
  2. ```
  3. Usage: scws [options] [[-i] input] [[-o] output]
  4. ```
  5. * _-i string|file_ 要切分的字符串或文件,如不指定则程序自动读取标准输入,每输入一行执行一次分词
  6. * _-o file_ 切分结果输出保存的文件路径,若不指定直接输出到屏幕
  7. * _-c charset_ 指定分词的字符集,默认是 gbk,可选 utf8
  8. * _-r file_ 指定规则集文件(规则集用于数词、数字、专有名字、人名的识别)
  9. * _-d file[:file2[:...]]_ 指定词典文件路径(XDB格式,请在 -c 之后使用)
  10. ```
  11. 自 1.1.0 起,支持多词典同时载入,也支持纯文本词典(必须是.txt结尾),多词典路径之间用冒号(:)隔开,
  12. 排在越后面的词典优先级越高。
  13. 文本词典的数据格式参见 scws-gen-dict 所用的格式,但更宽松一些,允许用不定量的空格分开,只有<词>是必备项目,
  14. 其它数据可有可无,当词性标注为“!”(叹号)时表示该词作废,即使在较低优先级的词库中存在该词也将作废。
  15. ```
  16. * _-M level_ 复合分词的级别:1~15,按位异或的 1|2|4|8 依次表示 短词|二元|主要字|全部字,缺省不复合分词。
  17. * _-I_ 输出结果忽略跳过所有的标点符号
  18. * _-A_ 显示词性
  19. * _-E_ 将 xdb 词典读入内存 xtree 结构 (如果切分的文件很大才需要)
  20. * _-N_ 不显示切分时间和提示
  21. * _-D_ debug 模式 (很少用,需要编译时打开 --enable-debug)
  22. * _-U_ 将闲散单字自动调用二分法结合
  23. * _-t num_ 取得前 num 个高频词
  24. * _-a [~]attr1[,attr2[,...]]_ 只显示某些词性的词,加~表示过滤该词性的词,多个词性之间用逗号分隔
  25. * _-v_ 查看版本

通过-M指定短词的复合分词,可以得到细粒度的分词。
默认是最大匹配:

点击(此处)折叠或打开

  1. [root@hanode1 tsearch_data]# scws -c utf8  -d dict.utf8.xdb  -r rules.utf8.ini "南京市长江大桥"
    南京市 长江大桥 
    +--[scws(scws-cli/1.2.2)]----------+
    | TextLen:   21                  |
    | Prepare:   0.0021    (sec)     |
    | Segment:   0.0003    (sec)     |
    +--------------------------------+


指定短词的复合分词,可以对长词再进行复合切分。

点击(此处)折叠或打开

  1. [root@hanode1 tsearch_data]# scws -c utf8  -d dict.utf8.xdb  -r rules.utf8.ini -M 1 "南京市长江大桥"
    南京市 南京 长江大桥 长江 大桥 
    +--[scws(scws-cli/1.2.2)]----------+
    | TextLen:   21                  |
    | Prepare:   0.0020    (sec)     |
    | Segment:   0.0002    (sec)     |
    +--------------------------------+

这样切分后"南京 & 大桥"也可以匹配。

甚至可以把重要的单字也切出来。

点击(此处)折叠或打开

  1. [root@hanode1 zhparser-0.1.4]# scws -c utf8  -d dict.utf8.xdb  -r rules.utf8.ini -M 5 "南京市长江大桥"
    南京市 南京 市 长江大桥 长江 大桥 江 桥 
    +--[scws(scws-cli/1.2.2)]----------+
    | TextLen:   21                  |
    | Prepare:   0.0020    (sec)     |
    | Segment:   0.0002    (sec)     |
    +--------------------------------+

这样切分后,"南京 & 桥"也可以匹配。

再变态一点,对短词和所有单字做复合切分。

点击(此处)折叠或打开

  1. [root@hanode1 zhparser-0.1.4]# scws -c utf8  -d dict.utf8.xdb  -r rules.utf8.ini -M 9 "南京市长江大桥"
    南京市 南京 南 京 市 长江大桥 长江 大桥 长 江 大 桥 
    +--[scws(scws-cli/1.2.2)]----------+
    | TextLen:   21                  |
    | Prepare:   0.0021    (sec)     |
    | Segment:   0.0003    (sec)     |
    +--------------------------------+

这样切分基本上可以不再遗漏匹配了,但是效率肯定受影响。
上面的选项是加在scws命令上的,也可以通过scws_set_multi()函数加到zhparser(libscws)上。
http://www.xunsearch.com/scws/docs.php#libscws:

点击(此处)折叠或打开

  1. 9. `void scws_set_multi(scws_t s, int mode)` 设定分词执行时是否执行针对长词复合切分。(例:“中国人”分为“中国”、“人”、“中国人”)。
  2.    > **参数 mode** 复合分词法的级别,缺省不复合分词。取值由下面几个常量异或组合:
  3.    >
  4.    > - SCWS_MULTI_SHORT 短词
  5.    > - SCWS_MULTI_DUALITY 二元(将相邻的2个单字组合成一个词)
  6.    > - SCWS_MULTI_ZMAIN 重要单字
  7.    > - SCWS_MULTI_ZALL 全部单字


修改zhparser.c,追加scws_set_multi()的调用
zhparser.c:

点击(此处)折叠或打开

  1. static void init(){
  2.         char sharepath[MAXPGPATH];
  3.         char * dict_path,* rule_path;
  4.         if (!(scws = scws_new())) {
  5.                 ereport(ERROR,
  6.                                 (errcode(ERRCODE_INTERNAL_ERROR),
  7.                                  errmsg("Chinese Parser Lib SCWS could not init!"%s"",""
  8.                                        )));
  9.         }
  10.         get_share_path(my_exec_path, sharepath);
  11.         dict_path = palloc(MAXPGPATH);
  12.         snprintf(dict_path, MAXPGPATH, "%s/tsearch_data/%s.%s",
  13.                         sharepath, "dict.utf8", "xdb");
  14.         scws_set_charset(scws, "utf-8");
  15.         scws_set_dict(scws,dict_path, SCWS_XDICT_XDB);
  16.         rule_path = palloc(MAXPGPATH);
  17.         snprintf(rule_path, MAXPGPATH, "%s/tsearch_data/%s.%s",
  18.                         sharepath, "rules.utf8", "ini");
  19.         scws_set_rule(scws ,rule_path);
  20.         scws_set_multi(scws ,SCWS_MULTI_SHORT|SCWS_MULTI_ZMAIN);//追加代码
  21. }


重新编译安装zhparser后,再restart PostgreSQL,可以看到效果。

点击(此处)折叠或打开

  1. testdb=# select to_tsvector('testzhcfg','南京市长江大桥');
  2.                                to_tsvector 
  3. -------------------------------------------------------------------------
  4.  '南京':2 '南京市':1 '大桥':6 '市':3 '桥':8 '江':7 '长江':5 '长江大桥':4
  5. (1 row)
  6. testdb=# select to_tsvector('testzhcfg','南京市长江大桥') @@ '南京 & 桥';
  7.  ?column? 
  8. ----------
  9.  t
  10. (1 row)


tsquery也会被复合切分:

点击(此处)折叠或打开

  1. testdb=# select to_tsquery('testzhcfg','南京市长江大桥');
  2.                               to_tsquery 
  3. -----------------------------------------------------------------------
  4.  '南京市' & '南京' & '市' & '长江大桥' & '长江' & '大桥' & '江' & '桥'
  5. (1 row)


这可能不是我们需要的,tsquery切的太细会影响查询效率。做了个简单的测试,走gin索引,按这个例子对tsquery复合切分会比默认的最大切分慢了1倍。

点击(此处)折叠或打开

  1. testdb=# d tb1
  2.     Table "public.tb1"
  3.  Column | Type | Modifiers 
  4. --------+------+-----------
  5.  c1 | text | 
  6. Indexes:
  7.     "tb1idx1" gin (to_tsvector('testzhcfg'::regconfig, c1))
  8. testdb=# insert into tb1 select '南京市长江大桥' from generate_series(1,10000,1);
  9. testdb=# explain analyze select count(*) from tb1 where to_tsvector('testzhcfg', c1) @@ '南京市 & 长江大桥'::tsquery;
  10.                                                            QUERY PLAN 
  11. --------------------------------------------------------------------------------------------------------------------------------
  12.  Aggregate (cost=348.53..348.54 rows=1 width=0) (actual time=6.077..6.077 rows=1 loops=1)
  13.    -> Bitmap Heap Scan on tb1 (cost=109.51..323.53 rows=10001 width=0) (actual time=3.186..4.917 rows=10001 loops=1)
  14.          Recheck Cond: (to_tsvector('testzhcfg'::regconfig, c1) @@ '''南京市'' & ''长江大桥'''::tsquery)
  15.          Heap Blocks: exact=64
  16.          -> Bitmap Index Scan on tb1idx1 (cost=0.00..107.01 rows=10001 width=0) (actual time=3.154..3.154 rows=10001 loops=1)
  17.                Index Cond: (to_tsvector('testzhcfg'::regconfig, c1) @@ '''南京市'' & ''长江大桥'''::tsquery)
  18.  Planning time: 0.117 ms
  19.  Execution time: 6.127 ms
  20. (8 rows)
  21. Time: 6.857 ms
  22. testdb=# explain analyze select count(*) from tb1 where to_tsvector('testzhcfg', c1) @@ '南京市 & 南京 & 市 & 长江大桥 & 长江 & 大桥 & 江 & 桥'::tsquery;
  23.                                                                                QUERY PLAN 
  24.                          
  25. ------------------------------------------------------------------------------------------------------------------------------------------------
  26. -------------------------
  27.  Aggregate (cost=396.53..396.54 rows=1 width=0) (actual time=10.823..10.823 rows=1 loops=1)
  28.    -> Bitmap Heap Scan on tb1 (cost=157.51..371.53 rows=10001 width=0) (actual time=7.923..9.631 rows=10000 loops=1)
  29.          Recheck Cond: (to_tsvector('testzhcfg'::regconfig, c1) @@ '''南京市'' & ''南京'' & ''市'' & ''长江大桥'' & ''长江'' & ''大桥'' & ''江''
  30.  & ''桥'''::tsquery)
  31.          Heap Blocks: exact=64
  32.          -> Bitmap Index Scan on tb1idx1 (cost=0.00..155.01 rows=10001 width=0) (actual time=7.885..7.885 rows=10000 loops=1)
  33.                Index Cond: (to_tsvector('testzhcfg'::regconfig, c1) @@ '''南京市'' & ''南京'' & ''市'' & ''长江大桥'' & ''长江'' & ''大桥'' & ''
  34. 江'' & ''桥'''::tsquery)
  35.  Planning time: 0.111 ms
  36.  Execution time: 10.879 ms
  37. (8 rows)
  38. Time: 11.586 ms

要回避这个问题可以做两套解析器,一套给tsvector用做复合切分;一套给tsquery用,不做复合切分。或者像上面测试例子中那样不对查询字符串做分词,由应用端直接输入tsquery(不过这样做会有别的问题,后面会提到)。

3.其它问题

3.1 '南大'被无视了

无意中发现一个奇怪的现象,'南大'被无视了:

点击(此处)折叠或打开

  1. testdb=# select to_tsvector('testzhcfg','南大') ;
  2.  to_tsvector 
  3. -------------
  4.  
  5. (1 row)


'北大','东大'甚至'西大'都没问题:

点击(此处)折叠或打开

  1. testdb=# select to_tsvector('testzhcfg','南大 北大 东大 西大') ;
  2.         to_tsvector 
  3. ----------------------------
  4.  '东大':2 '北大':1 '西大':3
  5. (1 row)

调查发现原因在于它们被SCWS解析出来的token类型不同:

点击(此处)折叠或打开

  1. testdb=# select ts_debug('testzhcfg','南大 北大 东大 西大') ;
  2.                 ts_debug 
  3. -----------------------------------------
  4.  (j,"abbreviation (jian lue)",南大,{},,)
  5.  (n,noun,北大,{simple},simple,{北大})
  6.  (n,noun,东大,{simple},simple,{东大})
  7.  (n,noun,西大,{simple},simple,{西大})
  8. (4 rows)


'南大'被识别为j(简略词),而之前并没有为j创建token映射。现在加上j的token映射,就可以了。

点击(此处)折叠或打开

  1. testdb=# ALTER TEXT SEARCH CONFIGURATION testzhcfg ADD MAPPING FOR j WITH simple;
  2. ALTER TEXT SEARCH CONFIGURATION
  3. testdb=# select to_tsvector('testzhcfg','南大 北大 东大 西大') ;
  4.              to_tsvector 
  5. -------------------------------------
  6.  '东大':3 '北大':2 '南大':1 '西大':4
  7. (1 row)

3.2 新词的识别

词典收录的词毕竟有限,遇到新词就不认识了。不断完善词典可以缓解这个问题,但不能从根本上避免。
'微信'没有被识别出来:

点击(此处)折叠或打开

  1. testdb=# select to_tsvector('testzhcfg','微信');
  2.   to_tsvector 
  3. ---------------
  4.  '信':2 '微':1
  5. (1 row)
  6. testdb=# select to_tsvector('testzhcfg','微信') @@ '微信';
  7.  ?column? 
  8. ----------
  9.  f
  10. (1 row)

虽然这个词没有被识别出来,但是我们只要对tsquery采用相同分词方法,就可以匹配。

点击(此处)折叠或打开

  1. testdb=# select to_tsvector('testzhcfg','微信') @@ to_tsquery('testzhcfg','微信');
  2.  ?column? 
  3. ----------
  4.  t
  5. (1 row)


但是,利用拆开的单字做匹配,检索的效率肯定不会太好。SCWS还提供了一种解决方法(-U),可以对连续的闲散单字做二元切分。

点击(此处)折叠或打开

  1. [root@hanode1 zhparser-0.1.4]# scws -c utf8  -d dict.utf8.xdb  -r rules.utf8.ini -U "微信微博"
    微信 信微 微博
    +--[scws(scws-cli/1.2.2)]----------+
    | TextLen:   12                  |
    | Prepare:   0.0020    (sec)     |
    | Segment:   0.0001    (sec)     |
    +--------------------------------+


对zhparser,可以像之前那样,修改zhparser.c,通过调用scws_set_duality()函数设置这个选项。
http://www.xunsearch.com/scws/docs.php#libscws

点击(此处)折叠或打开

  1. 10. `void scws_set_duality(scws_t s, int yes)` 设定是否将闲散文字自动以二字分词法聚合。
  2.    > **参数 yes** 如果为 1 表示执行二分聚合,0 表示不处理,缺省为 0。

但是二元切分也有缺点,会产生歧义词和无意义的词。而且如果这些连续的闲散单字真的是单字的话,二字聚合后就不能再做单字匹配了。

4. 总结

zhparser的安装和配置非常容易,分词效果也不错,可以满足一般的场景。如果有更高的要求需要做一些定制。

5. 参考

postgresql之全文搜索篇
http://www.postgresql.org/docs/9.4/static/textsearch.html
http://www.xunsearch.com/scws/docs.php
http://www.xunsearch.com/scws/api.php
http://amutu.com/blog/zhparser/
http://my.oschina.net/Kenyon/blog/82305?p=1#comments
http://blog.163.com/digoal@126/blog/static/163877040201252141010693/
http://francs3.blog.163.com/blog/static/405767272015065565069/
http://www.cnblogs.com/flish/archive/2011/08/08/2131031.html
http://wenku.baidu.com/link?url=wD7QgE8iNY-UshcSIWkVMUmpTa-dCsnYmn187XZhWuA5Hljt73raE25Wa8dFm_5IADD2T6y5Ur_JeCtouwszayjEUudLQN3pNJqZWN5ofFG
http://www.cnblogs.com/lvpei/archive/2010/08/04/1792409.html
http://blog.2ndquadrant.com/text-search-strategies-in-postgresql/
http://wenku.baidu.com/link?url=va4FRRibEfCdm731U420y5rxcnCDFTDY5Y7ElDbKdUNbusnEz8zLHt3bZlUaDqDQfLigkgycwdp4iWbRlvr2DV3P2bTeJlwipaNqNTughdK
http://jingyan.baidu.com/article/77b8dc7f2af94e6174eab6a2.html

免责声明:文章转载自《PostgreSQL全文检索zhparser使用》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇SIGPIPE信号的产生以及处理方法python实现多播数据的发送和接收下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

python使用jieba实现中文文档分词和去停用词

分词工具的选择:现在对于中文分词,分词工具有很多种,比如说:jieba分词、thulac、SnowNLP等。在这篇文档中,笔者使用的jieba分词,并且基于python3环境,选择jieba分词的理由是其比较简单易学,容易上手,并且分词效果还很不错。 分词前的准备: 待分词的中文文档 存放分词之后的结果文档 中文停用词文档(用于去停用词,在网上可以找到很多...

一个人工智能项目里的中文分词方案

做搜索的都知道,中文分词,一般都是先建一个词库,再根据词库进行分词。但是这样做有两个问题:1.存在歧义词,2.不容易发现新词。尤其第2点,在处理舆情类内容时更是如此。如果想减少这类问题,现在的主要解决手段,一般都是在后端建一个词条系统,通过累积找到高频词,然后把高频词加到词库中,再进行分词。但是这样做仍然有一个时间差问题,不适合即时性的内容判断。最近接手了...

ES之5:分词器

一、分词器概念 1、Analysis 和 Analyzer Analysis: 文本分析是把全文本转换一系列单词(term/token)的过程,也叫分词。Analysis是通过Analyzer来实现的。 当一个文档被索引时,每个Field都可能会创建一个倒排索引(Mapping可以设置不索引该Field)。 倒排索引的过程就是将文档通过Analyzer分...

Sphinx中文入门指南——新手可先看此文

文主要介绍Sphinx的入门使用,新手观看,老鸟指正! * 1、简介 * 1.1.Sphinx是什么 * 1.2.Sphinx的特性 * 1.3.Sphinx中文分词 * 2、安装配置实例 * 2.1 在GNU/Linux/unix系统上安装 o 2.1.1 sphinx安装 o 2.1.2.sfc安装(见另文) o 2.1.3.coreseek安装(见另文...

数据挖掘:基于Spark+HanLP实现影视评论关键词抽取(1)

1. 背景 近日项目要求基于爬取的影视评论信息,抽取影视的关键字信息。考虑到影视评论数据量较大,因此采用Spark处理框架。关键词提取的处理主要包含分词+算法抽取两部分。目前分词工具包较为主流的,包括哈工大的LTP以及HanLP,而关键词的抽取算法较多,包括TF-IDF、TextRank、互信息等。本次任务主要基于LTP、HanLP、Ac双数组进行分词,采...

mysql全文检索

mysql到版本3.23.23时,开始支持全文检索,通过语句SELECT ... FROM ... MATCH(...) AGAINST(...) 来在整个表中检索是否有匹配的,全文索引是一个定义为fulltext的类型索引,应用在myisam表中。值得一提的是对于一个大的数据库来说,把数据装载到一个没有fulltext索引的表中,然后再添加索引,这样速度...