【迅搜05】索引配置(二)字段定义与设计

索引配置(二)字段定义与设计

经过上篇文章的学习,我们已经了解到了 XS 中的默认索引配置是在哪里,也了解到了配置文件如何加载以及服务端的一些简单配置。今天,我们要学习的重点就是剩下的内容,也是非常重要的内容,那就是索引字段的配置定义以及字段设计。

字段选项

在 XS 中,每个字段都是使用一个区段配置来表示的。其实就是我们看到的中括号那个,中括号内容的是字段名,下面就是字段的选项。从一个字段到另一个字段之间的区域可以为上一个字段定义多个选项。

go 复制代码
[pid]
type = id

[subject]
type = title

比如上面这两个字段,表示的就是 pid 字段和 subject 字段,然后在它们的定义下面都一个字段选项,指定的是这两个字段的类型。那么接下来,我们就来看看类型代表的是什么意思。

type 字段类型

XS 的字段类型其实就和我们在 MySQL 中的字段类型是一样的。表示的是这个字段应该以什么格式来存储。同时,搜索引擎的字段类型还有别的更多的作用。

  • id 主键类型,确保每条数据具体唯一值,是索引更新和删除的凭据,每个索引配置文件中,只能有一个字段被设置为 id 类型,这个字段的值不区分大小写。注意:XS 中的这个主键类型,并没有唯一约束,只是说逻辑上我们插入的数据应该是唯一的,但是,我们是可以插入两条 id 值相同的数据的;另外,它是以字符串存储的,不是数字,因此,排序的时候会有问题,后面我们会详细说明并测试。

  • string 字符型,适用于大多数情况,也是默认值,也就是说,如果你在字段下面不写 type 类型,那么默认这个字段就是 string 类型。

  • numeric 数值型,包含整型和浮点型都是 numeric ,仅当字段需要用于排序或者区间检索时才设为这个类型,否则直接使用 string 类型来表示数字就行了。

  • date 日期类型,形式为 YYYYmmdd 这样的固定 8 字节字段,如果没有区间检索或排序的需求也不建议使用,直接用 string 类型就行了

  • title 标题类型,标题或名称字段,每个索引配置文件中也只有一个字段可以设置为 title 类型,本质上还是 string 类型,只不过多了一些其它默认的功能,主要体现在索引方式上,后面我们会说到。

  • body 内容类型,主内容字段,也就是我们索引配置中需要搜索的内容最长的字段,比如文章的内容,产品的描述等,一个配置文件中也只能有一个,作用和 tilte 类似,也是有特殊的默认索引属性的。

字段类型就是这几种,比起数据库来说还是少了很多了吧。比如我们的操作是以检索数据为主,因此,大部分情况下其实都使用的就是普通的 string 类型就可以了。numeric 和 date 类型通常是有特殊的需要,比如说排序或者区间搜索时会用到,而且这两种类型默认是不会分词的,也就是和 MySQL 中的字段是一样的功能,要索引也是整个字段的内容全部当成一个完整的值来进行索引。

而另外两个 title 和 body ,其实底层都是 string ,只是它们自己默认带了一些其它配置,我们马上就来说。

index 索引方式

索引方式和搜索方式有关。啥意思呢?在 XS 中,索引有 2 种搜索方式。

第一种,就是不标明字段的检索,之前我们的测试基本都是这种,也就是在 PHP 代码中 $xs->search->search('xxxx xxxx') 这样。这种会在 混合区 进行检索,返回的可能是 title 也可能是 body 中搜索到的内容,也可以是 string 字段配置了 index = bothindex = mixed 类型的字段。

第二种,标明特定字段的检索。用 PHP 代表表示就是 $xs->search->search('subject:xxxx') 这样,查询语句中有字段名表示。这种方式的检索又是不同的 index 配置。

关于搜索的方式我们后面会进行详细的学习,现在只要知道有这两种方式就好了。接下来我们就看看 index 这个配置具体的一些参数。

  • none 不做索引,所有的搜索匹配都和配置了这个属性的字段无关,一般作为排序或者搜索展示类型的字段会用到。

  • self 字段索引,就在是可以在搜索的时候,使用 字段名:xxx 来进行搜索的字段。

  • mixed 混合区索引,不标明字段,搜索的时候如果没有像上面一样使用字段名的话,就会在所有混合区索引相关的字段里面进行检索。

  • both 相当于 self + mixed ,这两种一起使用的效果。也就是带字段名,不带字段名都可以在这个字段上检索。

默认情况下,如果你不是 id、tilte 和 body 类型的字段,默认值都是 none 。

  • id 类型,默认是 self ,只能通过 id:xxx 这样来查询指定 id 的数据。

  • title 类型,默认是 both ,可以通过 title:xxx 来指定查询,也可以通过 xxx 这样和 body 一起混合查询。

  • body 类型,默认是 mixed ,不能通过 body:xxx 这样来查询,只能是 xxx 那样的混合查询。

这下明白上面的 type 类型为啥分出了三个特别的 id、title 和 body 类型了吧。上面这三种默认查询方式,其实也好理解。通常如果是针对 id 进行查询,都是想要进行精确匹配的,而且通常 id 这种字段也不应该是可以随便通过混合搜索词就可以查询到的。比如我想搜个 10 ,只要 tilte 和 body 中包含 10 就可以了,别把 id 等于 10 的搜索出来。如果确实是需要 id 为 10 的这条记录,就应该通过 id:10 这样精确地搜索。

剩下的 title 和 body 也是类似的概念,这里就不多解释了。

tokenizer分词器

XS 除了默认的 SCWS 分词之外,还可以在索引配置中为指定的字段设置不同的分词器。自带一些功能性的分词器,也可以指定自定义的分词器,在后面针对分词的学习时我们会进行更深入的学习,这里就先了解一下相关的配置有哪些。分词器配置的格式是可以带一个括号的,括号中可以有传递给分词器的参数,比如 tokenizer = split(,) 表示是使用逗号分隔,具体配置项我们一个一个来看下。

  • none 表示不进行分词

  • full 表示当前字段作为一个整体词,大部分的 id 以及数字、日期类型的字段都应该使用这种方式

  • split(arg) 表示根据参数分割内容,默认参数是空格,也就是默认使用空格来分词,如果以 / 开头并以 / 结尾,那么就会调用 PHP 的 preg_split() 函数来进行正则分词

  • xlen(arg) 表示根据 arg(数字)指定参数长度来分词,比如 xlen(2) ,就是按两个字的长度来分,ABCDEF 就会被分成 AB + CD + EF

  • xstep(arg) 表示根据 arg(数字)按步长分段取词,比如 xstep(2),对 ABCDEF 分词的话就是 AB + ABCD + ABCDEF 这样

  • scws(arg) 表示采用 arg(数字)参数指定的数量作为复合等级的 SCWS 分词配置(如果没有特殊的复合要求,就不要指定),这个等深入学习分词时再说

还有一个默认值 default ,其实就是 scws 的意思(不带任何参数)。默认情况下 id 会是 full ,而 title 和 body 以及其它类型都是 scws 。

cutlen 搜索结果摘要截取长度

这个配置项主要是针对某些内容特别长的字段,比如文章内容或者商品描述之类的,在返回结果时自动截取包含关键词的一小段文字。默认情况下 body 的配置是 300 ,单位是字节,也就是说按中文来说是 100 个中文字。对于其它类型字段,默认值都是 0 。

go 复制代码
cutlen = 0

weight 混合区检索时的概念权重

对于 index 是 mixed 或者 both 类型的字段来说,由于是可以多个字段同时检索的,所以就会带来一个权重问题。关键字出现在哪个字段中更重要呢?一般来说,title 相对 body 会更重要一些,因为,默认情况下,title 的 weight 是 5 ,而 body 是 1 。其它类型字段的默认值都是 1 。

go 复制代码
weight = 1

phrase 是否支持精确搜索

在进行搜索时,如果给搜索关键字加上引号,就表示匹配的结果必须严格按照引号中指定的顺序匹配,比如 "算法 数据结构",如果不支持精确搜索,那么这两个词谁在前谁在后都可以直接搜索到,而如果字段是支持精确搜索的,则必须"算法"在"数据结构"之前出现的内容才能匹配到。默认 tilte 和 body 都是 yes ,其它类型的默认值都是 no 。这个功能只支持默认分词器,另外,如无特殊要求,不建议使用,因为它会增加索引数据的大小。

go 复制代码
phrase = no

non_bool 强制指定是否为布尔索引

布尔索引是表示当前字段参不参与权重排名计算的,关于权重排名计算的内容我们后面也会详细说,这里也是先了解一下就好了。默认情况下所有的自定义分词器都是布尔索引的。因此,当使用自定义分词器,却又想让当前字段参与权重计算的话,就要将这个选项设置为 yes 。

go 复制代码
non_bool = yes

测试各种配置

主要的字段配置就是上面那些了,接下来我们就测试一下。这里使用的数据是我的所有博客文章,有 300 多篇。同时也展示一下如何从 MySQL 中导入数据。MySQL 的数据表结构是这样的。

go 复制代码
CREATE TABLE `zy_articles_xs_test` (
  `id` int NOT NULL,
  `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '标题',
  `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '内容',
  `category_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '分类名称',
  `tags` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '标签',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `pub_time` datetime DEFAULT NULL COMMENT '发布时间',
  `status` tinyint DEFAULT NULL COMMENT '状态:1已发布,0未发布',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

不多解释了,根据这个数据库表,我们可以定义下面这样一个索引配置文件。

go 复制代码
# 5-zyarticle-test1.ini
project.name = zyarticle
project.default_charset = utf-8

[id]
type = id

# 将默认的 index = both 换成了 mixed , phrase 设置为 no
[title]
type = title
index = mixed
phrase = no

# 将默认的 cutlen 从 300 设置为 30 , phrase 设置为 no
[content]
type = body
cutlen = 30
phrase = no


# 分词器指定为 full
[category_name]
type = string
index = both
tokenizer = full

# 分词器指定为逗号分词
# 支持精确搜索,没效果的
# 权重调到 10 ,默认 title 是 5 ,body 固定是 1
[tags]
type = string
index = both
tokenizer = split(,)
phrase = yes
weight = 10

# 类型是日期,不索引
[pub_time]
type = date
index = none

为什么少了一些字段呢?这个具体的设计问题,我们放到最后再说,这个索引配置文件中,我们主要是用来测试上面学习到的那一堆配置选项用得。具体的内容在注释中都写得很清楚了。

导入测试数据

首先,我们先使用 SDK 提供的工具来导入测试数据。其实和我们之前的方式是一样的,只不过之前我们是使用 csv 类型,然后手动输入测试数据。这回换成 MySQL 配置,然后直接去查询表导入。

go 复制代码
> php vendor/hightman/xunsearch/util/Indexer.php --source=mysql://root:@localhost/zyblog ./config/5-zyarticle-test1.ini --sql="select * from zy_articles_xs_test where status = 1" --clean
清空现有索引数据 ...
初始化数据源 ... mysql://root:@localhost/zyblog 
开始批量导入数据 (请直接输入数据) ...
完成索引导入:成功 339 条,失败 0 条
刷新索引提交 ...

很方便吧,数据导入后就可以查询测试了。

go 复制代码
>  php vendor/hightman/xunsearch/util/Quest.php --show-query ./config/5-zyarticle-test1.ini ""        
--------------------
解析后的 QUERY 语句:Query(<alldocuments>)
--------------------
在 339 条数据中,大约有 339 条包含  ,第 1-10 条,用时:0.0203 秒。

1. 【PHP数据结构与算法1】在学数据结构和算法的时候我们究竟学的是啥? #1# [100%,0.00]
<h1>在学数据结构和算法的时候我们究竟学的是啥?</h1><p>一说到数据结构与... 
Category_name:PHP  Tags:数据结构,算法  Pub_time:20220723  
..............................

官方自带的这个工具,不仅可以导入 MySQL ,还可以像之前一样导入 CSV ,如果指定了文件就可以直接从 CSV 文件中批量读取导入数据。另外,它也可以直接导入 JSON、SQLite 数据。具体用法可以不加参数运行 /util/Indexer.php 来查看帮助信息。

这种方式导入的原始数据有个问题,那就是文章内容我们是带 HTML 标签的,但是在搜索的时候我们是不希望有 HTML 标签干扰的。那么咱们就还是通过 PHP 代码来导入吧,这样比较好过滤数据。

go 复制代码
require_once 'vendor/autoload.php';

$dns = 'mysql:host=localhost;dbname=zyblog;port=3306;charset=utf8';
$pdo = new PDO($dns, 'root', '', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC]);


$stmt = $pdo->prepare("select * from zy_articles_xs_test");
$stmt->execute();

$list = $stmt->fetchAll();


$xs = new XS("./config/5-zyarticle-test1.ini");
$xs->index->clean();

foreach($list as $v){
    $v['content'] = strip_tags($v['content']);
    $doc = new XSDocument($v);
    $xs->index->add($doc);
}

echo '索引建立完成!';

PHP 添加数据的代码之前就见了,也就不多解释了,主要就是使用 strip_tags() 函数简单过滤了一下 HTML 标签。现在的数据就比较干净了。

go 复制代码
> php 5.php 
索引建立完成!%    

## 等一会吧
> php vendor/hightman/xunsearch/util/Quest.php --show-query ./config/5-zyarticle-test1.ini ""
--------------------
解析后的 QUERY 语句:Query(<alldocuments>)
--------------------
在 339 条数据中,大约有 339 条包含  ,第 1-10 条,用时:0.0295 秒。

1. 【PHP数据结构与算法1】在学数据结构和算法的时候我们究竟学的是啥? #1# [100%,0.00]
在学数据结构和算法的时候我们究竟学的是啥?一说到数据结构与算法,大... 
Category_name:PHP  Tags:数据结构,算法  Pub_time:20220723 
..............................

测试 title 改成 mixed 后的效果

默认情况下,或者你删掉上面配置中的 index = mixed,使用 title:算法 是可以搜索到东西的,但是因为我们设置成只能通过混合区检索了,那么这样指定字段名的搜索就无效啦。

go 复制代码
>  php vendor/hightman/xunsearch/util/Quest.php --show-query ./config/5-zyarticle-test1.ini "title:算法"
--------------------
解析后的 QUERY 语句:Query((Zsubject@1 AND 算法@2))
--------------------
在 339 条数据中,大约有 0 条包含 subject:算法 ,第 0-0 条,用时:0.0371 秒。

不信吗?不信自己试试呗,修改个配置文件,然后使用上面写好的 PHP 代码重建索引。

go 复制代码
# 去掉 title 下面的 index = mixed ,然后再次运行 php 5.php
> php vendor/hightman/xunsearch/util/Quest.php --show-query ./config/5-zyarticle-test1.ini "title:算法"
--------------------
解析后的 QUERY 语句:Query(B算法@1)
--------------------
在 339 条数据中,大约有 23 条包含 title:算法 ,第 1-10 条,用时:0.0291 秒。

1. 【PHP数据结构与算法8】PHP数据结构及算法总结 #21# [100%,4.84]
PHP数据结构及算法总结断断续续地把这个系列写完了,就像上一个设计模式... 
Category_name:PHP  Tags:数据结构,算法  Pub_time:20220723  
........................

后面的测试就不这么啰嗦啦,就看一下实验效果,然后其它的效果大家自己去尝试就好啦。另外在 title 这个字段上,我们还做了一个改变,那就是设置了 phrase = no ,默认情况下,它是 yes 。

包括后面 content 也设置成了 no ,现在,不管用不用精确搜索,都达不到精确搜索的效果了。精确搜索的意思就是,比如我们的文章里有一篇文章的标题是:【PHP数据结构与算法7.2】交换排序:冒泡、快排(有彩蛋)。搜索 ""快排冒泡"",如果是 phrase 为 yes 的状态,那么是搜不出东西的,分词后,冒泡必须要在快排前面,这就是精确搜索的意思。但是我们设置 title 和 content 的 phrase 为 no 之后,精确搜索就没有效果了,不管前后都可以搜索到。

go 复制代码
# 尝试修改 title 和 content 的 phrase ,看看有什么区别
> php vendor/hightman/xunsearch/util/Quest.php --show-query ./config/5-zyarticle-test1.ini '"快排冒泡"' 
> php vendor/hightman/xunsearch/util/Quest.php --show-query ./config/5-zyarticle-test1.ini '"冒泡快排"'

测试不索引时间

在配置文件中,我们将 pub_time 字段设置为了 index = none ,也就是不对这个字段的内容进行索引。先来随便查找一条数据。

go 复制代码
> php vendor/hightman/xunsearch/util/Quest.php --show-query ./config/5-zyarticle-test1.ini "算法7.1"
--------------------
解析后的 QUERY 语句:Query((信管@1 AND 1.15@2))
--------------------
在 339 条数据中,大约有 1 条包含 信管1.15 ,第 1-1 条,用时:0.0246 秒。

1. 【PHP数据结构与算法7.1】插入类排序:简单插入、希尔排序 #18# [100%,7.57]
...入我们的排序相关算法的学习了。相信不管是系统学习过的还是没有系统学...
Category_name:PHP  Tags:数据结构,算法  Pub_time:20220723

然后根据返回的 pub_time 时间,再去搜索这个时间值。

go 复制代码
> php vendor/hightman/xunsearch/util/Quest.php --show-query ./config/5-zyarticle-test1.ini "20220723"
--------------------
解析后的 QUERY 语句:Query(20220723@1)
--------------------
在 339 条数据中,大约有 0 条包含 20220723 ,第 0-0 条,用时:0.0119 秒。
> php vendor/hightman/xunsearch/util/Quest.php --show-query ./config/5-zyarticle-test1.ini "pub_time:20220723"
--------------------
解析后的 QUERY 语句:Query(0 * F20221114)
--------------------
在 339 条数据中,大约有 0 条包含 pub_time:20220723 ,第 0-0 条,用时:0.0121 秒。

很明显,不管是字段检索还是混合区检索都查不到数据。

id 字段可以不是唯一的

我们来添加一条数据。

go 复制代码
$doc = new XSDocument([
  'id'=>100001,
  'title'=> '测试tags逗号分词和category_name全值索引及字段索引',
  'content'=>'电路原理图时实时路况多久地板砖南昌中专学校晨进棒喝杨万里中',
  'tags'=>'电路原,理图,时实时,路况多,久地板砖,南昌中专学校晨进棒喝杨万里中',
  'category_name'=>'时实时'
]);
$xs->index->add($doc);

因为 id 字段不是唯一的,所以上面的这条可以执行两次,看看效果。

go 复制代码
> php vendor/hightman/xunsearch/util/Quest.php --show-query ./config/5-zyarticle-test1.ini '逗号分词'       
--------------------
解析后的 QUERY 语句:Query((逗号@1 AND 分词@2))
--------------------
在 341 条数据中,大约有 2 条包含 逗号分词 ,第 1-2 条,用时:0.0137 秒。

1. 测试tags逗号分词和category_name全值索引及字段索引 #100001# [100%,14.39]
电路原理图时实时路况多久地板砖南昌中专学校晨进棒喝杨万里中 
Category_name:时实时  
Tags:电路原,理图,时实时,路况多,久地板砖,南昌中专学校晨进棒喝杨万里中 
Pub_time:  

2. 测试tags逗号分词和category_name全值索引及字段索引 #100001# [100%,14.39]
电路原理图时实时路况多久地板砖南昌中专学校晨进棒喝杨万里中 
Category_name:时实时  
Tags:电路原,理图,时实时,路况多,久地板砖,南昌中专学校晨进棒喝杨万里中 
Pub_time:

查询出来的结果中,第一条两个 ## 号中间的就是 id 字段的内容,可以看到,这是两条一样的 id 的数据。然后排序试试。

go 复制代码
> php vendor/hightman/xunsearch/util/Quest.php --show-query ./config/5-zyarticle-test1.ini "算法" --sort=id       
--------------------
解析后的 QUERY 语句:Query(算法@1)
--------------------
在 341 条数据中,大约有 63 条包含 算法 ,第 1-10 条,用时:0.0193 秒。

1. 【PHP数据结构与算法1】在学数据结构和算法的时候我们究竟学的是啥? #1# [100%,2.88]
在学数据结构和算法的时候我们究竟学的是啥?一说到数据结构与算法,大... 
Category_name:PHP  Tags:数据结构,算法  Pub_time:20220723  

2. 【PHP数据结构与算法4.2】二叉树的遍历及逻辑操作 #10# [85%,2.45]
二叉树的遍历及逻辑操作上篇文章我们讲了许多理论方面的知识,虽说很枯... 
Category_name:PHP  Tags:数据结构,算法  Pub_time:20220723  
........................

第二条数据就不对了吧,怎么是 id 10 的数据先出来了呢?其实呀,这就是因为咱们的 id 不是数字类型的,也就是说,这个 id 我们给字符串也可以。字符串在排序的时候会按照字符逐一比对,因此,第一个字符是 1 的全部完了才会到 2 开头的。那么咱们就是想要用 id 来排序呢?其实大家可以冗余一个 id 字段,比如这样。

go 复制代码
[sortid]
type=numeric
index=none

也就是指定一个类型为数字的字段,同步样地也存放 id ,然后排序的时候使用这个字段就可以了。另外,时间类型也是经常可以用于排序的。

go 复制代码
> php vendor/hightman/xunsearch/util/Quest.php --show-query ./config/5-zyarticle-test1.ini "算法" --sort=~pub_time
--------------------
解析后的 QUERY 语句:Query(算法@1)
--------------------
在 341 条数据中,大约有 63 条包含 算法 ,第 1-10 条,用时:0.0156 秒。

1. 【信管1.15】安全(二)加解密技术 #843# [94%,2.73]
...义;需要采用加密算法提取信息的特征码(校验码)或特征矢量,并与有关... 
Category_name:项目产品  Tags:信管师  Pub_time:20221114  

2. 【信管1.13】新技术(二)大数据与移动互联网 #841# [66%,1.92]
...些都是大数据和 AI 算法在背后做着各种推荐计算。如果说这些 APP 有一些商... 
Category_name:项目产品  Tags:信管师  Pub_time:20221109  
..............................

官方 SDK 工具中的 --sort 参数就是可以指定排序字段的,也可以指定多个,在字段前面加上一个 ~ 表示的就是倒序。大家可以试试我们时间倒序的这个结果是不是对的。

body 类型测试

首先来看一下,body 类型默认只有混合区检索的,是没有字段检索的,也就是下面这种用法是查不到数据的。

go 复制代码
> php vendor/hightman/xunsearch/util/Quest.php --show-query ./config/5-zyarticle-test1.ini 'content:电路' 
--------------------
解析后的 QUERY 语句:Query((Zcontent@1 AND 电路@2))
--------------------
在 341 条数据中,大约有 0 条包含 content:电路 ,第 0-0 条,用时:0.0096 秒。

只能通过不加字段名的混合区检索查询到数据。

go 复制代码
> php vendor/hightman/xunsearch/util/Quest.php --show-query ./config/5-zyarticle-test1.ini '电路'         
--------------------
解析后的 QUERY 语句:Query(电路@1)
--------------------
在 341 条数据中,大约有 3 条包含 电路 ,第 1-3 条,用时:0.0148 秒。

1. 测试tags逗号分词和category_name全值索引及字段索引 #100001# [100%,6.65]
电路原理图时实时路况多久地板砖南昌中专学校晨进棒喝杨万里中 
Category_name:时实时  
Tags:电路原,理图,时实时,路况多,久地板砖,南昌中专学校晨进棒喝杨万里中 
Pub_time: 
..................

接着就是对于 body 类型的 content 字段,我们设置了 cutlen=30 ,这个需要用 PHP 代码测一下,官方 SDK 的 Quest.php 工具中没有相关的参数配置。

go 复制代码
$docs = $xs->search->search('');
var_dump($docs[0]->content);

运行查询后,可以看到返回的内容是:

go 复制代码
> php 5.php 
string(33) "在学数据结构和算法的..."

只有十个中文字符。那么如何获取完整的所有 body 字段内容呢?其实只要设置为 cutlen=0 就可以了,大家可以试一下。不过,并不推荐!!

对于 body 类型,本身就是为特别大的字段准备的,因此,它默认给了 300 的截取长度。而其它类型的字段,通常不会有那么大,所以都是完整返回的。对于非常长的字段,其实各个搜索引擎都是更推荐由搜索引擎返回数据库主键 ID ,然后通过 ID 去数据查找。比如 Sphinx 就是完全只返回一个 ID 的。而 XS 在默认情况下会返回搜索词出现的前后 300 字节的内容,这已经很不错啦。太长的内容,最主要的问题就是数据返回的时间会拉长,特别是进行列表搜索时。试想列表中的每个文档都是完整的数据内容,即使是 10 篇文章的列表,也会有非常大的内容,并且占用非常大的传输带宽。

而且,各位在使用 Baidu 或者 Google 时也会发现,在列表上的内容,也就是返回标题和内容中包含关键词的一部分。和我们 XS 返回的内容是完全一样的。如果确实需要完整的内容数据,那么就使用主键 ID 去数据库查询,主键聚集索引在数据库中的查询速度是非常快的。另外还有一种,就是本身 body 类型的字段不会有太多的内容,比如电商中的商品详情,可能图片居多,文字较少,这时是可以考虑直接 cutlen=0 的。

tags逗号分词

对于 tags 这个字段,我们配置了很多东西,type = stringindex = both 就不多解释了。先来看一下逗号分词的问题。

go 复制代码
> php vendor/hightman/xunsearch/util/Quest.php --show-query ./config/5-zyarticle-test1.ini "tags:地板" 
--------------------
解析后的 QUERY 语句:Query(0 * E地板)
--------------------
在 341 条数据中,大约有 0 条包含 tags:地板 ,第 0-0 条,用时:0.0092 秒。

正常来说,"地板"是可以分成词的,但是我们指定 tags 字段检索时,竟然搜不到?别急,tags 已经按逗号分词了,所以我们就得按那个诡异的词来分了。

go 复制代码
> php vendor/hightman/xunsearch/util/Quest.php --show-query ./config/5-zyarticle-test1.ini "tags:久地板砖" 
--------------------
解析后的 QUERY 语句:Query(0 * E久地板砖)
--------------------
在 341 条数据中,大约有 2 条包含 tags:久地板砖 ,第 1-2 条,用时:0.0081 秒。

1. 测试tags逗号分词 #100001# [100%,0.00]
电路原理图时实时路况多久地板砖南昌中专学校晨进棒喝杨万里中 
Category_name:时实时  
Tags:电路原,理图,时实时,路况多,久地板砖,南昌中专学校晨进棒喝杨万里中 
Pub_time:  

2. 测试tags逗号分词 #100001# [100%,0.00]
电路原理图时实时路况多久地板砖南昌中专学校晨进棒喝杨万里中 
Category_name:时实时  
Tags:电路原,理图,时实时,路况多,久地板砖,南昌中专学校晨进棒喝杨万里中 
Pub_time:

哈哈,这就是我们定义的按逗号分词的效果。一切以我们指定的分隔符号为基准,进行分隔。正则或者其它符号的测试,大家可以自己试试哦。

对于 phrase 的设置,因为这里的分词使用的不是 SCWS 默认分词,所以,phrase 的配置不会生效,大家可以自己试试哦。

最后,我们把 tags 的权重提高了,前面的介绍中就说过,title 类型的默认权重为 5 ,body 固定为 1 ,现在我们将 tags 提升为 10 ,然后再插入一条数据。

go 复制代码
$doc = new XSDocument([
        'id'=>100001,
        'title'=> '252525',
        'content'=>'11223344',
        'tags'=>'逗号分词',
        'category_name'=>'时实时'
    ]);
    $xs->index->add($doc);

暂时可以先理解为,权重高的优先级就高,因此,不出意外,搜索 tags 中包含的词,这条新插入的数据会排到前面来。

go 复制代码
php vendor/hightman/xunsearch/util/Quest.php --show-query ./config/5-zyarticle-test1.ini '逗号分词' 
--------------------
解析后的 QUERY 语句:Query((逗号@1 AND 分词@2))
--------------------
在 342 条数据中,大约有 3 条包含 逗号分词 ,第 1-3 条,用时:0.0235 秒。

1. 252525 #100001# [100%,14.60]
11223344 
Category_name:时实时  Tags:逗号分词  Pub_time:  

2. 测试tags逗号分词 #100001# [93%,13.65]
电路原理图时实时路况多久地板砖南昌中专学校晨进棒喝杨万里中 
Category_name:时实时  
Tags:电路原,理图,时实时,路况多,久地板砖,南昌中专学校晨进棒喝杨万里中 
Pub_time:  
........................

你可以自己试试去掉 tags 字段的 weight=10,然后再试试搜索结果是怎样的。

category_name整体分词及字段索引

最后的这个字段,没什么特别的,就是设置了 tokenizer = full 这样一个完整索引的配置,咱们来看看效果。

go 复制代码
> php vendor/hightman/xunsearch/util/Quest.php --show-query ./config/5-zyarticle-test1.ini category_name:实时   
--------------------
解析后的 QUERY 语句:Query(0 * D实时)
--------------------
在 342 条数据中,大约有 0 条包含 category_name:实时 ,第 0-0 条,用时:0.0080 秒。

针对这个字段,如果只搜索项目这个词,是搜不到东西的。因为对于这个字段来说,我们是将它的值完整地作为一个索引词内容的。因此,我们需要完整的分类名称。

go 复制代码
php vendor/hightman/xunsearch/util/Quest.php --show-query ./config/5-zyarticle-test1.ini category_name:时实时
--------------------
解析后的 QUERY 语句:Query(0 * D时实时)
--------------------
在 103 条数据中,大约有 3 条包含 category_name:时实时 ,第 1-3 条,用时:0.0099 秒。

1. 测试tags逗号分词 #100001# [100%,0.00]
电路原理图时实时路况多久地板砖南昌中专学校晨进棒喝杨万里中
Category_name:时实时
Tags:电路原,理图,时实时,路况多,久地板砖,南昌中专学校晨进棒喝杨万里中
Pub_time:  Sortid:

2. 252525 #100001# [100%,0.00]
11223344
Category_name:时实时  Tags:逗号分词  Pub_time:  Sortid:

3. 电路原理图时实时路况多久地板砖南昌中专学校晨进棒喝杨万里中 #100001# [100%,0.00]
测试tags逗号分词
Category_name:时实时  Tags:123123  Pub_time:  Sortid:
........................

最终的索引设计

好了好了,搞了半天,其实上面咱们都是在测试各种配置效果。真正的进行业务开发时,肯定不能这么玩呀。那么对于搜索引擎来说,应该怎样进行字段设计呢?

虽说 ES 是可以直接生成 Mapping 的,可以不用预先定义索引结构。但是,就像 Mongodb 一样,即使它们很灵活,但过于灵活的设计会是将来维护的恶梦。因此,一个好的字段及类型设计,就是非常重要的部分。也可以避免我们将来出现问题时需要频繁地重建索引。XS 也是可以在 PHP 的代码中动态定义字段的,这个我们后面会学习到。

对于类似的搜索引擎来说,最佳的字段设计通常是反范式的。也就是说要违反数据库的设计范式,比如我的实际文章表远比上面那个表结构复杂。文章和文章内容、标签和标签关系、分类与文章的关系,都是会开不同的表的。但是,在搜索引擎中,如果这些字段需要被搜索,那么就应该都拿过来。

其次,上面的数据库表中,status 字段其实用处不大,因为我们进入搜索引擎的应该就是已发布的内容,因为,在插入索引时,就直接通过 SQL 语句过滤掉了 status 不为 1 的其它数据,只保存已布的文章。这样的话,status 字段就不需要设计到 XS 的字段中了。

最后,create_time 我也没要,这也是和我的文章设计有关的,因为在前台我只显示发布时间,创建时间没用。因此,这个字段也没加进来。

最后,合理地字段类型也是非常重要的。从 XS 的字段类型就可以看出,XS 对文章类应用真的是非常友好的,自带 title 和 body 两种类型。

好了,将来我们还需要使用这些文章数据进行其它的学习测试,因此,咱们最后就按下面这个配置文件确定我们最终要使用的索引配置文件吧。

go 复制代码
project.name = zyarticle
project.default_charset = utf-8

[id]
type = id

[title]
type = title

[content]
type = body

[category_name]
type = string
index = both
tokenizer = full

[tags]
type = string
index = both
tokenizer = split(,)

[pub_time]
type = date
index = none

总结

内容很多,但其实都是贴的代码。主要是咱们进行了很多的测试。其实呀,经过今天的文章,大部分同学已经可以拿 XS 进行日常的业务开发了。索引设计好了,其实就已经解决了很大一部分问题了。毕竟大部分情况下,我们的搜索查询真的就只是把关键字传进去就行了。后面要学习的搜索相关的技巧,大部分只是锦上添花的效果。

好了,索引配置相关的内容就结束了,后面我们还将继续学习的是在 PHP 代码中动态管理索引相关的内容。怎么样,感觉有意思嘛?扩展学习的话就是大家可以再去看一下 ES 中的索引以及 Mapping 相关的内容。

测试代码:

https://github.com/zhangyue0503/dev-blog/blob/master/xunsearch/source/5.php

参考文档:

http://www.xunsearch.com/doc/php/guide/ini.guide