近段时间的一个新项目,使用了分表之后,mysql 做一些搜索就只能使用联表做视图,对搜索很不友好,我们选择了,`sphinx/coreseek`,因为基本能满足我们的需求,当然还是其他的`Xunsearch`,`Lucene`,都是不错。这整个过程中,我们遇到了不少的问题,,希望以下内容能帮助到你。 ### 环境版本 > * os:centos 6.6 64位,ceonts 7在编译coreseek时会报各种automake等错误 > * php5.4 + mysql5.6 ### 安装过程略过 网上有很多安装之类的文章。 官方文档:http://www.coreseek.cn/docs/coreseek_4.1-sphinx_2.0.1-beta.html 详细安装 http://blog.csdn.net/e421083458/article/details/21529969 **如果你想做搜索的表主键是bigint类型,请在编辑时加上`--enable-id64`** ``` ./configure --prefix=/usr/local/coreseek --without-unixodbc --with-mmseg --with-mmseg-includes=/usr/local/mmseg3/include/mmseg/ --with-mmseg-libs=/usr/local/mmseg3/lib/ --with-mysql --enable-id64 ``` ## 使用过程中碰到的问题 ### 1、主键Id为bigint,长度为20位 我们将用户表分了50个单表,如`user_0,user_1,...user_49`,里面的用户ID,都是使用mysql的`uuid_short()`函数生成,如果使用多库的情况下请使用同一台服务器生成uuid,不然长度不会统一。如果在编译过程中没有加`--enable-id64`,那对不起,你搜索的结果不对,但你又看不出什么错误,需要重新编译安装 ### 2、主键是字符串,怎么破 这是官方说的:**文档ID必须是第一列,而且必须是唯一的正整数值(不能是0也不能是负数),既可以是32位的也可以是64位的**。所以说没有办法,只能添加一列,然后使用uuid_short(),更新并添加索引。 ### 3、怎么做模糊搜索 在中文分词时,如`中国人`,在搜索`中`,没有结果,这怎么能忍,发现搜索拆分的词为`中国`,`人`,`国人`,没有将词单个拆分,其实官方说明文档里有。 **MMSEG分词配置选项** mmseg分词相关的配置选项,需要保存到文件mmseg.ini,并将该配置文件放置到charset_dictpath所设置的目录中。 ``` 基本配置: [mmseg] seperate_number_ascii=1; ;就是将字母和数字打散; ``` **`这个地方请注意,开启之后生成索引的文件会比之前大很多`** ### 4、分表或多表联合查询时应该怎么建立索引 之前我们join或复杂的sql时,发现维护起来很麻烦,可以先在数据库中新建视图,以方便做搜索。 ### 5、如何更方便的配置管理多库数据库 ``` source server { type = mysql sql_host = 127.0.0.1 sql_user = db_user sql_pass = db_password sql_port = 3306 sql_query_pre = SET NAMES utf8 } #test_user为库名 #view_user为视图表名,继承server source test_user_view_user : server { sql_db = test_user sql_query = select uid,nick,source,ctime from test_view_user sql_field_string = nick #可全文搜索,可返回原始文本信息 sql_attr_string = source sql_attr_string = ctime sql_query_info = select * from test_view_user where uid = $uid } #test_content为库名 #view_content为视图表名,继承server source test_content_view_content : server { sql_db = test_content sql_query = select content_id,ctime,content from taoo_view_content sql_field_string = content sql_attr_string = ctime sql_query_info = select * from taoo_view_content where content_id = $content_id } ``` 这样配置数据,可以在`server`源中做更多的基础配置,如用户密码统一管理,还可以做增量数据索引更新 ### 6、配置多个索引 ``` index test_index_user_view_user { source = test_user_view_user docinfo = extern mlock = 0 morphology = none min_word_len = 1 ngram_len = 1 min_infix_len = 0 #这里有重要 charset_type = zh_cn.utf-8 #这里有重要 charset_dictpath = /usr/local/mmseg3/etc/ html_strip = 0 path = /usr/local/coreseek/var/data/taoo_index_user_view_user } #这是继承前面的索引,没有找到更简洁的写法 index test_index_content_view_note_info : test_index_user_view_user { source = test_content_view_content path = /usr/local/coreseek/var/data/test_index_content_view_note_info } ``` ### 7、基础数据源,官网贴的 ``` indexer { #最大可能的限制是2047M,官方说的 mem_limit = 1048M } searchd { listen = 9312 listen = 9306:mysql41 log = /usr/local/coreseek/var/log/searchd.log query_log = /usr/local/coreseek/var/log/query.log read_timeout = 5 client_timeout = 300 max_children = 30 pid_file = /usr/local/coreseek/var/log/searchd.pid max_matches = 1000 seamless_rotate = 1 preopen_indexes = 1 unlink_old = 1 mva_updates_pool = 1M max_packet_size = 8M max_filters = 256 max_filter_values = 4096 max_batch_queries = 32 workers = threads # for RT to work } ``` ## php 简单操作类 ``` 1, 'page_count' => 1, 'item_count' => 0, 'item_per_page' => 20, 'item_list' => [], ]; /** * @var \SphinxClient */ private $conn; /** * @return Sphinx */ static public function Instance() { $class = get_called_class(); if (empty(self::$instance)) { self::$instance = new $class(); } return self::$instance; } /** * 建立链接 * * @return Sphinx */ public function init() { $this->conn = new \SphinxClient(); $this->conn->setServer(self::$host, self::$port); $this->conn->setMatchMode(SPH_MATCH_ANY);//匹配查询词中的任意一个. $this->conn->setMaxQueryTime($this->timeout); $this->conn->SetArrayResult(true);//控制搜索结果集的返回格式 return $this; } /** * 执行搜索查询 * * @param $keyword * @param $indexName * @return $this * @throws Exception */ public function query($keyword, $indexName) { if (empty($keyword) || empty($indexName)) { throw new Exception('query error: keyword or indexName is null'); } $this->_keywords = $keyword; $this->_indexName = $indexName; return $this; } /** * 字段 * * @param string $field * @return $this */ public function field($field = '*') { $this->conn->SetSelect($field); return $this; } public function getError() { return $this->conn->GetLastError(); } /** * 给服务器端结果集设置一个偏移量($offset)和从那个偏移量起向客户端返回的匹配项数目限制($limit)。 * 并且可以在服务器端设定当前查询的结果集大小($max_matches),另有一个阈值($cutoff),当找到的匹配项达到这个阀值时就停止搜索。 * 全部这些参数都必须是非负整数。 * $page是需要获取的页数 * @param $page * @param int $itemPerPage * @param int $maxMatches * @param int $cutoff * @return $this */ public function limit($page, $itemPerPage = 20, $maxMatches = 1000, $cutoff = 0) { $this->setPage(); $this->_page['page'] = $page; $offset = ($page - 1) * $itemPerPage; $this->_page['item_per_page'] = $itemPerPage; $this->conn->SetLimits($offset, $this->_page['item_per_page'], $maxMatches, $cutoff); return $this; } /** * 排序 * * @param $field * @param string $orderBy * @return $this * @throws Exception */ public function order($field, $orderBy = 'asc') { if (empty($field)) { throw new Exception('order error: field is null'); } $this->conn->SetSortMode(SPH_SORT_EXTENDED, "{$field} $orderBy"); return $this; } public function sortMode($mode,$sortBy) { if (empty($mode)) { throw new Exception('order error: field is null'); } $this->conn->SetSortMode($mode, $sortBy); return $this; } public function indexWeight($weight){ $this->conn->SetIndexWeights($weight); return $this; } public function match($desc = SPH_MATCH_ALL) { switch ($desc) { case SPH_MATCH_ALL : $this->conn->SetMatchMode(SPH_MATCH_ALL); break; case SPH_MATCH_ANY: $this->conn->SetMatchMode(SPH_MATCH_ANY); break; case SPH_MATCH_PHRASE : $this->conn->SetMatchMode(SPH_MATCH_PHRASE); break; case SPH_MATCH_BOOLEAN : $this->conn->SetMatchMode(SPH_MATCH_BOOLEAN); break; case SPH_MATCH_EXTENDED : $this->conn->SetMatchMode(SPH_MATCH_EXTENDED); break; case SPH_MATCH_FULLSCAN : $this->conn->SetMatchMode(SPH_MATCH_FULLSCAN); break; default : $this->conn->SetMatchMode(SPH_MATCH_EXTENDED2); break; } return $this; } public function ranking($desc = SPH_RANK_PROXIMITY_BM25) { switch ($desc) { case SPH_RANK_PROXIMITY_BM25 : $this->conn->SetRankingMode(SPH_RANK_PROXIMITY_BM25); break; case SPH_RANK_BM25 : $this->conn->SetRankingMode(SPH_RANK_BM25); break; case SPH_RANK_NONE : $this->conn->SetRankingMode(SPH_RANK_NONE); break; default : $this->conn->SetRankingMode(SPH_RANK_PROXIMITY_BM25); break; } return $this; } /** * 获得数据 * * @param bool $filter * @return array */ public function get($filter = true) { $this->_data = $this->conn->query($this->_keywords, $this->_indexName); return $filter ? $this->_filterData() : $this->_data; } /** * 清空数据,以便二次查询 */ public function setPage() { $this->_page = [ 'page' => 1, 'page_count' => 1, 'item_count' => 0, 'item_per_page' => 20, 'item_list' => [], ]; } /** * 过滤结果数据 * * @return array */ private function _filterData() { $itemList = []; if (!empty($this->_data['total']) && !empty($this->_data['matches'])) { $data = array_values($this->_data['matches']); foreach ($data as $value) { $itemList[] = array_merge(['id' => $value['id']], $value['attrs']); } $itemPerPage = min(50, $this->_page['item_per_page']); $pageCount = ceil($this->_data['total'] / $itemPerPage); $this->_page['page_count'] = $pageCount; $this->_page['item_count'] = $this->_data['total']; $this->_page['item_list'] = $itemList; $this->_page['words'] = $this->_data['words']; } $this->_page['words'] = $this->_data['words']; return $this->_data = $this->_page; } } //使用方法也很简单 $sphinx = Sphinx::Instance()->init(); $result = $search->query($keyWord, $indexName)->limit($page, $itemPerPage)->order('ctime','desc')->field($field)->get(); ``` ### 参考文档 http://www.coreseek.cn/docs/coreseek_4.1-sphinx_2.0.1-beta.html#conf-rt-mem-limit http://blog.csdn.net/e421083458/article/details/21529969