MySQL中文全文檢索解決方案


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;
}     


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM