Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 432|回复: 0

MySQL中文全文检索解决方案

[复制链接]
  • TA的每日心情
    奋斗
    2024-11-24 15:47
  • 签到天数: 804 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726782
    发表于 2021-7-17 15:43:09 | 显示全部楼层 |阅读模式

    PHP+MySQL构架的网站中,大数据量的全文检索一般都会用到MySQLFULLTEXT全文索引,通过SELECT...MATCH...AGAINST语句来进行查找。

    迄今为止,MySQL对中文全文索引无法正确支持,MySQL是不会识别中文词语的。参照MySQL识别英文单词机制,要建立中文全文索引,暂时的解决方案只有手动将中文分词(以空格的形式将中文词语分开),来将中文转换成MySQL认识的语言。

     

    如今网上对于中文分词的解决方案有很多,有基于MySQL插件的,有谈论算法思想的。基于插件(如海量科技的MySQL--LinuxX86-Chinese+hightman开发的mysql--ft-hightman)的方式主要通过对MySQL数据库安装一个别人提供好的插件,在建FULLTEXT索引的字段时后面加上WITH PARSER ×××(大多都是这样)的形式。而基于算法思想的则大部分工作都要自己完成,但他们的大体思想都差不多:

    1. 对插入的要建全文索引的中文数据进行分词;

    2. 将原始数据和分词后的数据都存入数据库中,并以某种方式建立联系;

    3. 在存储分词数据的字段上建立FULLTEXT索引;

    4. 查询时以SELECT...MATCH...AGAINST的方式在分词字段上搜索,将搜到的行通过前面建立的联系找到原始数据行并返回。

     

    而我们在讨论解决方案时,考虑到使用开源插件的话可控性比价差,而且插件会对MySQL做一些改变,我们决定将分词存储的工作自己写代码完成,这样虽然工作量加大,但以后的维护成本却降低了很多。下面我们来看下大体实现。

     

     

    一、首先,先建立数据库

    要注意的是只有MyISAM表类型才能支持FULLTEXTMyISAMInnoDB各有优劣,我们决定将原始数据与索引分表存储,原始数据存入InnoDB表,同时建立MyISAM表存入作为检索的字段和用于关联的字段id。这里我建立了两张表questionsquestions_idx

    其中,titledetail是要建立全文索引的字段,而id则是建立两张表的联系。值得注意的是,MyISAM是不支持事务和外键的,因此对于两张表数据的同步还要靠额外代码逻辑来实现。

    还有就是索引字段有可能需要比原始数据更大的空间,这里我分配了2倍(这个是我随意想的,有可能需要更多)。

     

    二、接着,讨论中文分词

    网上流传的分词方法有很多,主要有基于算法的(比如二元分词算法,字节交叉切分算法)和基于词库的。基于算法是不必要维护词库的,而词库法则必须维护词库,有可能跟不上词汇的发展。实际上现在很多著名的搜索引擎都使用了多种分词的办法,比如正向最大匹配+逆向最大匹配,基于统计学的新词识别,自动维护词库等技术

    我们采用的是基于词库的,并且使用了hightmanscwsphp扩展模块方式。参考http://www.ftphp.com/scws/ 。这个开源分词系统这里不多说,总之利用的是词库来分词,而且最新版的是支持自定义词库的,这对于我们的内部网站来说,词库的维护问题变得简单了,因为新增词汇不会像外部网站那么大,也不需要维护太多。

     

    下面定义了类CWS,方法get_idx将输入中文数据,输出分词并编码后的数据:

     

    class CWS {
      //对输入字符串使用scws进行分词,去重复项,进行urlencode编码
      public static function get_idx($input) {
        //--------分词-----------
        $so = scws_new();
        $so->set_charset('utf8');
        $so->set_ignore(true);
        $output = '';
        $so->send_text($input);
        while ($tmp = $so->get_result()) {
          foreach ($tmp as $item) {
            $output .= $item['word'] . ' ';
          }
        }
        $so->close();
        //--------编码-----------
        $data = array_filter(explode(" ",$output)); //删除数组空项
        $data = array_flip(array_flip($data));      //删除重复项
        //对分词结果进行urlcode编码
        foreach ($data as $ss) {
          if (strlen($ss) > 1) {
            $data_code .= str_replace('%','',urlencode($ss)) . ' ';
          }
        }
        return $data_code;
      }
    }
    

     

    对于scws的那段代码请参照scws使用手册。

    而对于为什么要进行编码,网上大都解释是:MySQL系统自变量规定了全文检索被编入索引单词的最小长度和最大长度(ft_min_word_lenft_max_word_len),默认的最小值为4个字符默认的最大值取决于使用的 MySQL 版本。 参考http://dev.mysql.com/doc/refman/4.1/en/server-system-variables.html#sysvar_ft_min_word_len 

    为了不改变这个默认值同时也是兼考虑这个值对于英文的意义,则需要通过编码将中文词变长。

     

    而对于编码,网上流传的方式也有很多,base64编码、urlencode编码等,甚至还有汉字转拼音。这里我尝试了urlencode编码,需要注意的是urlencode会产生很多%,这在MySQL中是通配符,要去掉。

     

    三、插入数据

    插入数据的时候我们就要调用以上的函数了:

    public function add($title, $detail, $askerid) {
      $date = NOW;
      $sql = 'INSERT INTO questions ' .
          '(title, detail, askerid, date) ' .
          'values ' .
          "('$title', '$detail', $askerid, '$date')";
      $result = $this->db->query($sql);
      $id = $this->db->lastId();
      //scws分词存储
      $title_idx = CWS::get_idx($title);
      //使用strip_tags函数过滤掉富文本编辑器产生的标签
      $detail_idx = CWS::get_idx(strip_tags($detail));
      $sql = 'INSERT INTO questions_idx ' .
          '(id, title, detail) ' .
          'values ' .
          "($id, '$title_idx', '$detail_idx')";
      $this->db->query($sql);
      return $result;
    }
    

     

    四、搜索数据

    搜索的时候,从输入框获取问题,当然如果你输入的是关键词,那就直接搜就是了,但我们是要用户输入的一个完整的问题,因此也要分词,否则MySQL还是检索不到:

    public function search($word, $limit) {
        $word = CWS::get_idx($word);
        $sql = "SELECT A.title, A.detail, askerid, date " .
               "FROM questions as A, questions_idx as B " .
               "WHERE A.id = B.id " .
               "AND MATCH (B.title, B.detail) AGAINST ('$word')";
        $result = $this->db->getAll($sql, $limit);
        return $result;
    }     
    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-12-23 03:14 , Processed in 0.063509 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表