form1.cn
Make a little progress every day

Sphinx+PHP扩展方式简单配置与使用

08th of March 2017 Linux Sphinx 2160

复制默认配置文件,重新创建一个配置文件

sphinx.conf.dist是完整版默认配置,有很多内容,我这里选择复制的是sphinx-min.conf.dist迷你版,只要满足基本查询需要即可

# cp /usr/local/sphinx/etc/sphinx-min.conf.dist  /usr/local/sphinx/etc/sphinx.conf


sphinx.conf文件的配置说明

#
# Minimal Sphinx configuration sample (clean, simple, functional)
#
//数据源设置
source src1
{
    // 下面是sql数据库特有的类型,地址,端口,用户名,密码,编码,数据库名等。
    type= mysql 
    sql_host= 127.0.0.1
    sql_user= root
    sql_pass= 123456
    sql_db= yiiadmin
    sql_port= 3306# optional, default is 3306
    sql_query_pre   = SET NAMES utf8
}
//创建一个数据源,他的信息继承自 src1,继承和程序类的继承一样,子对象可以对父对象的值进行覆盖。使用继承可以省略我们编写重复的设置信息。
source article : src1
{
    //要建立索引的SQL
    sql_query = SELECT id,id as aid,class_one_id,class_two_id,ctime...... FROM yi_article
    //下面是属性
    sql_attr_uint = aid  //注:sphinx不能使用主键来做属性字段 sql_attr_uint = id (id为表的主键)
    sql_attr_uint = class_one_id
    sql_attr_uint = class_two_id
    sql_attr_timestamp = ctime
}
//索引设置
index articleindex
{
    // 索引类型,包括有plain,distributed和rt。分别是普通索引/分布式索引/增量索引。默认是plain。
    type            = plain
    // 索引数据源
    source= article
    // 索引文件存放路径
    path= /usr/local/sphinx22/var/data/article
    // docinfo指的就是数据的所有属性(field)构成的一个集合
    docinfo         = extern
    // 设置了mlock就不会出现这个问题,这部分数据会一直存放在内存中的
    mlock           = 0
    // 所以英语的词形处理器会讲dogs当做dog来进行处理
    morphology      = none
    // 最小索引词长度,小于这个长度的词不会被索引
    min_word_len    = 1
    // 字符集编码类型,可以为sbcs,utf-8,当前加上会报错,原因未知
    #charset_type    = utf-8
    //##### 字符表,注意:如使用这种方式,则sphinx会对中文进行单字切分,
    //##### 即进行字索引,若要使用中文分词,必须使用其他分词插件如 coreseek,sfc
    //目前发现的情况是,如果不加中文将不匹配
    charset_table = U+FF10..U+FF19->0..9, 0..9, U+FF41..U+FF5A->a..z, U+FF21..U+FF3A->a..z,A..Z->a..z, a..z, U+0149, U+017F, U+0138, U+00DF, U+00FF, U+00C0..U+00D6->U+00E0..U+00F6,U+00E0..U+00F6, U+00D8..U+00DE->U+00F8..U+00FE, U+00F8..U+00FE, U+0100->U+0101, U+0101,U+0102->U+0103, U+0103, U+0104->U+0105, U+0105, U+0106->U+0107, U+0107, U+0108->U+0109,U+0109, U+010A->U+010B, U+010B, U+010C->U+010D, U+010D, U+010E->U+010F, U+010F,U+0110->U+0111, U+0111, U+0112->U+0113, U+0113, U+0114->U+0115, U+0115, U+0116->U+0117,U+0117, U+0118->U+0119, U+0119, U+011A->U+011B, U+011B, U+011C->U+011D, U+011D,U+011E->U+011F, U+011F, U+0130->U+0131, U+0131, U+0132->U+0133, U+0133, U+0134->U+0135,U+0135, U+0136->U+0137, U+0137, U+0139->U+013A, U+013A, U+013B->U+013C, U+013C,U+013D->U+013E, U+013E, U+013F->U+0140, U+0140, U+0141->U+0142, U+0142, U+0143->U+0144,U+0144, U+0145->U+0146, U+0146, U+0147->U+0148, U+0148, U+014A->U+014B, U+014B,U+014C->U+014D, U+014D, U+014E->U+014F, U+014F, U+0150->U+0151, U+0151, U+0152->U+0153,U+0153, U+0154->U+0155, U+0155, U+0156->U+0157, U+0157, U+0158->U+0159, U+0159,U+015A->U+015B, U+015B, U+015C->U+015D, U+015D, U+015E->U+015F, U+015F, U+0160->U+0161,U+0161, U+0162->U+0163, U+0163, U+0164->U+0165, U+0165, U+0166->U+0167, U+0167,U+0168->U+0169, U+0169, U+016A->U+016B, U+016B, U+016C->U+016D, U+016D, U+016E->U+016F,U+016F, U+0170->U+0171, U+0171, U+0172->U+0173, U+0173, U+0174->U+0175, U+0175,U+0176->U+0177, U+0177, U+0178->U+00FF, U+00FF, U+0179->U+017A, U+017A, U+017B->U+017C,U+017C, U+017D->U+017E, U+017E, U+0410..U+042F->U+0430..U+044F, U+0430..U+044F,U+05D0..U+05EA, U+0531..U+0556->U+0561..U+0586, U+0561..U+0587, U+0621..U+063A, U+01B9,U+01BF, U+0640..U+064A, U+0660..U+0669, U+066E, U+066F, U+0671..U+06D3, U+06F0..U+06FF,U+0904..U+0939, U+0958..U+095F, U+0960..U+0963, U+0966..U+096F, U+097B..U+097F,U+0985..U+09B9, U+09CE, U+09DC..U+09E3, U+09E6..U+09EF, U+0A05..U+0A39, U+0A59..U+0A5E,U+0A66..U+0A6F, U+0A85..U+0AB9, U+0AE0..U+0AE3, U+0AE6..U+0AEF, U+0B05..U+0B39,U+0B5C..U+0B61, U+0B66..U+0B6F, U+0B71, U+0B85..U+0BB9, U+0BE6..U+0BF2, U+0C05..U+0C39,U+0C66..U+0C6F, U+0C85..U+0CB9, U+0CDE..U+0CE3, U+0CE6..U+0CEF, U+0D05..U+0D39, U+0D60,U+0D61, U+0D66..U+0D6F, U+0D85..U+0DC6, U+1900..U+1938, U+1946..U+194F, U+A800..U+A805,U+A807..U+A822, U+0386->U+03B1, U+03AC->U+03B1, U+0388->U+03B5, U+03AD->U+03B5,U+0389->U+03B7, U+03AE->U+03B7, U+038A->U+03B9, U+0390->U+03B9, U+03AA->U+03B9,U+03AF->U+03B9, U+03CA->U+03B9, U+038C->U+03BF, U+03CC->U+03BF, U+038E->U+03C5,U+03AB->U+03C5, U+03B0->U+03C5, U+03CB->U+03C5, U+03CD->U+03C5, U+038F->U+03C9,U+03CE->U+03C9, U+03C2->U+03C3, U+0391..U+03A1->U+03B1..U+03C1,U+03A3..U+03A9->U+03C3..U+03C9, U+03B1..U+03C1, U+03C3..U+03C9, U+0E01..U+0E2E,U+0E30..U+0E3A, U+0E40..U+0E45, U+0E47, U+0E50..U+0E59, U+A000..U+A48F, U+4E00..U+9FBF,U+3400..U+4DBF, U+20000..U+2A6DF, U+F900..U+FAFF, U+2F800..U+2FA1F, U+2E80..U+2EFF,U+2F00..U+2FDF, U+3100..U+312F, U+31A0..U+31BF, U+3040..U+309F, U+30A0..U+30FF,U+31F0..U+31FF, U+AC00..U+D7AF, U+1100..U+11FF, U+3130..U+318F, U+A000..U+A48F,U+A490..U+A4CF
    //N-Gram是指不按照词典,而是按照字长来分词,这个主要是针对非英文体系的一些语言来做的(中文、韩文、日文)
    ngram_len = 1
    //加上这个选项,则会对每个中文,英文字词进行分割,速度会慢,当前情况如不设置匹配不出来数据
    ngram_chars = U+4E00..U+9FBB, U+3400..U+4DB5, U+20000..U+2A6D6, U+FA0E, U+FA0F, U+FA11, U+FA13, U+FA14, U+FA1F, U+FA21, U+FA23, U+FA24, U+FA27, U+FA28, U+FA29, U+3105..U+312C, U+31A0..U+31B7, U+3041, U+3043, U+3045, U+3047, U+3049, U+304B, U+304D, U+304F, U+3051, U+3053, U+3055, U+3057, U+3059, U+305B, U+305D, U+305F, U+3061, U+3063, U+3066, U+3068, U+306A..U+306F, U+3072, U+3075, U+3078, U+307B, U+307E..U+3083, U+3085, U+3087, U+3089..U+308E, U+3090..U+3093, U+30A1, U+30A3, U+30A5, U+30A7, U+30A9, U+30AD, U+30AF, U+30B3, U+30B5, U+30BB, U+30BD, U+30BF, U+30C1, U+30C3, U+30C4, U+30C6, U+30CA, U+30CB, U+30CD, U+30CE, U+30DE, U+30DF, U+30E1, U+30E2, U+30E3, U+30E5, U+30E7, U+30EE, U+30F0..U+30F3, U+30F5, U+30F6, U+31F0, U+31F1, U+31F2, U+31F3, U+31F4, U+31F5, U+31F6, U+31F7, U+31F8, U+31F9, U+31FA, U+31FB, U+31FC, U+31FD, U+31FE, U+31FF, U+AC00..U+D7A3, U+1100..U+1159, U+1161..U+11A2, U+11A8..U+11F9, U+A000..U+A48C, U+A492..U+A4C6 
    // html标记清理,是否从输出全文数据中去除HTML标记
    html_strip      = 0
}
// 建立索引
indexer
{
    // 建立索引的时候,索引内存限制
    mem_limit= 32M
}
// 检索服务
searchd
{
    // 监听端口
    listen= 9312
    listen= 9306:mysql41
    // 监听日志
    log    = /usr/local/sphinx22/var/log/searchd.log
    // 查询日志
    query_log= /usr/local/sphinx22/var/log/query.log
    // 客户端读超时时间 
    read_timeout= 5
    // 并行执行搜索的数目
    max_children= 30
    // 进程id文件
    pid_file= /usr/local/sphinx22/var/log/searchd.pid
    // 无缝轮转。防止 searchd 轮换在需要预取大量数据的索引时停止响应
    // 当进行索引轮换的时候,可能需要消耗大量的时间在轮换索引上。
    // 但是启动了无缝轮转,就以消耗内存为代价减少轮转的时间
    seamless_rotate= 1
    // 索引预开启,是否强制重新打开所有索引文件
    preopen_indexes= 1
    // 索引轮换成功之后,是否删除以.old为扩展名的索引拷贝
    unlink_old= 1
    // 多处理模式(MPM)。 可选项;可用值为none、fork、prefork,以及threads。 默认在Unix类系统为form,Windows系统为threads
    workers= threads # for RT to work
    // 二进制日志路径
    binlog_path= /usr/local/sphinx22/var/data
}


生成索引文件

# /usr/local/sphinx22/bin/indexer --all //会生成配置文件下所有索引
Sphinx 2.2.11-id64-release (95ae9a6)
Copyright (c) 2001-2016, Andrew Aksyonoff
Copyright (c) 2008-2016, Sphinx Technologies Inc (http://sphinxsearch.com)
using config file '/usr/local/sphinx22/etc/sphinx.conf'...
indexing index 'articleindex'...
collected 67 docs, 0.5 MB
sorted 0.1 Mhits, 100.0% done
total 67 docs, 524532 bytes
total 0.033 sec, 15713960 bytes/sec, 2007.18 docs/sec
total 4 reads, 0.000 sec, 47.3 kb/call avg, 0.0 msec/call avg
total 12 writes, 0.000 sec, 29.2 kb/call avg, 0.0 msec/call avg

注:报错:FATAL: failed to lock /usr/local/sphinx22/var/data/article.spl: Resource temporarily unavailable, will not index. Try --rotate option.

解决:

/usr/local/sphinx22/bin/indexer --all --rotate  //强制更新索引


启动服务

# /usr/local/sphinx22/bin/searchd //启动进程
Sphinx 2.2.11-id64-release (95ae9a6)
Copyright (c) 2001-2016, Andrew Aksyonoff
Copyright (c) 2008-2016, Sphinx Technologies Inc (http://sphinxsearch.com)
using config file '/usr/local/sphinx22/etc/sphinx.conf'...
listening on all interfaces, port=9312
listening on all interfaces, port=9306
precaching index 'articleindex'
precached 1 indexes in 0.020 sec 
# /usr/local/sphinx/bin/searchd --stop  //停止



简单测试

以前 sphinx 的 bin 目录里面有个自带 search 程序,新版本没有了,所以只好使用api或扩展方式调用了。

本文使用的扩展方式,关于sphinx的安装与php扩展安装,请查看本站相关文章


$sphinx = new SphinxClient;
//sphinx的主机名和端口
$sphinx->setServer('127.0.0.1',9312);
//设置返回结果集为php数组格式
$sphinx->SetArrayResult ( true );
//匹配结果的偏移量,参数的意义依次为:起始位置,返回结果条数,最大匹配条数
$sphinx->SetLimits(0, 20, 1000); //可用来实现分页
//最大搜索时间
$sphinx->SetMaxQueryTime(10);
//执行简单的搜索,这个搜索将会查询所有字段的信息(不包括设置的属性字段)
$result = $sphinx->query('PHP','articleindex');//多个关键词 (服务端配置)|(详解)
//索引源是配置文件中的 articleindex ,如果有多个索引源可使用,号隔开:'email,diary' 或者使用''号代表全部索引源
print_r($result);
//注:属性不参与索引查询,返回结果中只会有数据源中设置的属性
//部分返回结果
Array
(
    [error] => 
    [warning] => 
    [status] => 0
    [fields] => Array
        (
            [0] => class_one
            [1] => class_two
            [2] => title
            [3] => image
            [4] => is_home
            [5] => is_release
            [6] => sort
            [7] => content
            [8] => click_num
            [9] => manager_id
        )
    [attrs] => Array
        (
            [aid] => 1
            [class_one_id] => 1
            [class_two_id] => 1
            [ctime] => 2
        )
    [matches] => Array
        (
            [47] => Array
                (
                    [weight] => 3144
                    [attrs] => Array
                        (
                            [aid] => 47
                            [class_one_id] => 139
                            [class_two_id] => 158
                            [ctime] => 1479722607
                        )
                )
            [39] => Array
                (
                    [weight] => 3084
                    [attrs] => Array
                        (
                            [aid] => 39
                            [class_one_id] => 139
                            [class_two_id] => 158
                            [ctime] => 1478050647
                        )
                )
        )
    [total] => 67
    [total_found] => 67
    [time] => 0.044
    [words] => Array
        (
            [php] => Array
                (
                    [docs] => 67
                    [hits] => 768
                )
        )
)
//$result是一个数组,其中
//total是匹配到的数据总数量
//matches是匹配的数据,包含id,attrs这些信息
//words是搜索关键字的分词


你可能奇怪为什么没有title的内容这些信息,其实sphinx并不会返回像mysql那样的数据数组,因为sphinx本来就没有记录完整的数据,只记录被分词后的数据。

具体还要看matches数组,matches中的ID就是指配置文件中sql_query SELECT语句中的第一个字段,我们配置文件中是这样的

sql_query = SELECT id,id as aid,class_one,class_one_id,class_two,class_two_id,title,image,is_home,is_release,sort,content,click_num,manager_id,ctime FROM yi_article


所以matches中的ID是指id

至于weight是指匹配的权重,一般权重越高被返回的优先度也最高,匹配权重相关内容请参考官方文档

attrs是配置文件中sql_attr_    中的信息,稍后会提到这些属性的用法

 

说了这么多,即使搜索到结果也不是我们想要的email数据,但事实sphinx是不记录真实数据的,所以要获取到真实email数据还要根据matches中的ID去搜索mysql的email表,但总体来说这样一来一回的速度还是远远比mysql的LIKE快得多,前提是几十万数据量以上,否则用sphinx只会更慢。

 

接下来介绍sphinx一些类似mysql条件的用法


//id的范围
$sphinx->SetIdRange($min, $max); 
//属性过滤,可过滤的属性必需在配置文件中设置sql_attr_    ,之前我们定义了这些
sql_attr_uint = aid
sql_attr_uint = class_one_id
sql_attr_uint = class_two_id
sql_attr_timestamp = ctime
//如果你想再次修改这些属性,配置完成后记得重新建立索引才能生效
//指定一些值
$sphinx->SetFilter('fromid', array(1,2));    //fromid的值只能是1或者2
//和以上条件相反,可增加第三个参数
$sphinx->SetFilter('fromid', array(1,2), false);    //fromid的值不能是1或者2
//指定一个值的范围
$sphinx->SetFilterRange('toid', 5, 200);    //toid的值在5-200之间
//和以上条件相反,可增加第三个参数
$sphinx->SetFilterRange('toid', 5, 200, false);    //toid的值在5-200以外
//执行搜索
$result = $sphinx->query('关键字', '');

 

排序模式


可使用如下模式对搜索结果排序:


SPH_SORT_RELEVANCE 模式, 按相关度降序排列(最好的匹配排在最前面)


SPH_SORT_ATTR_DESC 模式, 按属性降序排列 (属性值越大的越是排在前面)


SPH_SORT_ATTR_ASC 模式, 按属性升序排列(属性值越小的越是排在前面)


SPH_SORT_TIME_SEGMENTS 模式, 先按时间段(最近一小时/天/周/月)降序,再按相关度降序


SPH_SORT_EXTENDED 模式, 按一种类似SQL的方式将列组合起来,升序或降序排列。


SPH_SORT_EXPR 模式,按某个算术表达式排序


//使用属性排序
//以fromid倒序排序,注意当再次使用SetSortMode会覆盖上一个排序
$sphinx->SetSortMode ( 'SPH_SORT_ATTR_DESC', 'fromid');
//如果要使用多个字段排序可使用SPH_SORT_EXTENDED模式
//@id是sphinx内置关键字,这里指emailid,至于为什么是emailid,自己思考一下
$sphinx->SetSortMode ( 'SPH_SORT_ATTR_DESC', 'fromid ASC, toid DESC, @id DESC');
//执行搜索
$result = $sphinx->query('关键字', '');
//更多请查看官方文档排序模式的说明


匹配模式


有如下可选的匹配模式:


SPH_MATCH_ALL, 匹配所有查询词(默认模式);


SPH_MATCH_ANY, 匹配查询词中的任意一个;


SPH_MATCH_PHRASE, 将整个查询看作一个词组,要求按顺序完整匹配;


SPH_MATCH_BOOLEAN, 将查询看作一个布尔表达式 


SPH_MATCH_EXTENDED, 将查询看作一个CoreSeek/Sphinx内部查询语言的表达式 . 从版本Coreseek 3/Sphinx 0.9.9开始, 这个选项被选项SPH_MATCH_EXTENDED2代替,它提供了更多功能和更佳的性能。保留这个选项是为了与遗留的旧代码兼容——这样即使Sphinx及其组件包括API升级的时候,旧的应用程序代码还能够继续工作。


SPH_MATCH_EXTENDED2, 使用第二版的“扩展匹配模式”对查询进行匹配.


SPH_MATCH_FULLSCAN, 强制使用下文所述的“完整扫描”模式来对查询进行匹配。注意,在此模式下,所有的查询词都被忽略,尽管过滤器、过滤器范围以及分组仍然起作用,但任何文本匹配都不会发生.


我们要关注的主要是SPH_MATCH_EXTENDED2扩展匹配模式,扩展匹配模式允许使用一些像mysql的条件语句


//设置扩展匹配模式
$sphinx->SetMatchMode ( 'SPH_MATCH_EXTENDED2' );
//查询中使用条件语句,字段用@开头,搜索内容包含测试,toid等于1的邮件:
$result = $sphinx->query('@content (测试) & @toid =1', '');
//用括号和&(与)、|、(或者)、-(非,即!=)设置更复杂的条件
$result = $sphinx->query('(@content (测试) & @subject =呃) | (@fromid -(100))', '');


//更多语法请查看官方文档匹配模式的说明


扩展匹配模式中值得一提的是搜索的字段,如果该字段被设置属性,那么扩展匹配搜索的字段默认是不包含这些属性的,只能用SetFilter()或者SetFilterRange()之类


之前我们设置了fromid、toid、sendtime为属性,但又想在扩展匹配模式中又想用作条件该怎么办?


只要在sql_query语句中再选择多一次该字段就可以了

sql_query = SELECT emailid,fromid,fromid,toid,toid,subject,content,sendtime,sendtime,attachement FROM email


//设置完成记得重新建立索引


更多条件技巧


只是一些技巧,但不建议使用的部署环境中,至于为什么,请看文章结尾


<、<=、>、>=


默认sphinx没有这些比较符。


假如我想邮件的发送时间大于某一日期怎么办?用SetFilterRange()方法模拟一下

 

//大于等于某一时间截$time
$sphinx->SetFilterRange('sendtime', $time, 10000000000) //时间截最大是10个9,再加1是不可超越了。。
//大于某一时间截$time
$sphinx->SetFilterRange('sendtime', $time+1, 10000000000)
//小于等于某一时间截$time
$sphinx->SetFilterRange('sendtime', -1, $time)    //时间截最小是0,所以应该减1
//大于某一时间截$time
$sphinx->SetFilterRange('sendtime', -1, $time - 1)

 

IS NOT NULL


怎样搜索为空的字段,比如我要搜索附件为空的邮件,有人可能会想 @attachment ('')不就可以了吗?其实这是搜索两个单引号。。。sphinx搜索的字符串不用加引号的


目前sphinx是没有提供这样的功能,其实可以在mysql语句上作手脚:


sql_query = SELECT emailid,fromid,toidsubject,content,sendtime,attachement != '' as attach is not null FROM email //这里返回了一个新字段attachisnotnull,当attachisnotnull为1的时候附件就不为空了


//设置完成记得重新建立索引


FIND_IN_SET()


搜索包含某一附件的邮件,mysql习惯用FIND_IN_SET这么简单一句就搞定了,在sphinx中必需在配置里设置属性sql_attr_multi 多值属性(MVA):


sql_attr_multi    = attachment    #attachment可以是逗号分隔的附件ID,或者是空格、分号等sphinx都能识别


//设置完成记得重新建立索引
然后PHP中可以使用SetFilter()
//搜索包含附件ID为1或2邮件,mysql语法是这样FIND_IN_SET(`attachment`, '1,2')
$sphinx->SetFilter('attachment', array(1,2))
//可以使用SetFilterRange,搜索包含附件ID在50-100范围的邮件
$sphinx->SetFilterRange('attachment', 50, 100)

 

总结

如果你想一个免费、好用、极速的全文搜索引擎,sphinx无疑是最好的选择,但是不要忘记sphinx的目的:全文检索。不要去想那些乱七八糟条件。你想要把sphinx搜索变得像mysql那样灵活,可完全单独用在一些复杂的多条件搜索,像某些邮件的高级搜索,那么我建议你还是多花点时间在PHP或者mysql代码的优化上,因为那样可能会让你的搜索变得更慢。

最好的方法是以最简单的方法搜索到内容,将ID交还mysql数据库搜索。