狮身人面像3#

Sphinx是免费的双重许可搜索服务器。Sphinx用C ++编写,专注于查询性能和搜索相关性。

当前的主要客户端API是SphinxQL(SQL的一种方言)。几乎任何MySQL连接器都可以使用。此外,还提供了用于多种语言(PHP,Python,Ruby,C,Java)的基本HTTP / JSON API和本机API。

本文档旨在为Sphinx v.3.x及更高版本构建更好的文档。把它当作一本你可以真正阅读的书或教程; 可以将先前的“参考手册”视为“字典”,在其中您可以查找特定的语法功能。两者可能(并且应该)最终融合。

功能概述#

顶级图片,Sphinx提供什么?

  • SQL,HTTP / JSON和自定义本机SphinxAPI访问API
  • NRT(近实时)和脱机批索引
  • 全文和非全文(参数)搜索
  • 关联性排名,从基本公式到ML模型
  • 来自多台服务器的联合结果
  • 表现不错

其他似乎值得一提的事情(此列表可能在任何时候都不完整,而且绝对是随机的):

  • 形态学和文本处理工具
  • 完全灵活的令牌化(请参阅charset_tableexceptions
  • 适用于英语,俄语和德语的正确形态学(lemmatizer)(请参阅参考资料morphology
  • 许多其他语言的基本形态(词干)
  • 用户指定的字形, core 2 duo => c2d
  • 本机JSON支持
  • 地理搜索支持
  • 快速表情引擎
  • 查询建议
  • 片段生成器

当然,总有一些我们知道我们目前缺乏的东西!

  • 索引复制

功能备忘单#

本节应该提供所有可用功能的更多详细信息。或多或少地覆盖他们;并为您提供更多指向特定参考部分的指针(在相关的config指令和SphinxQL语句上)。

  • 全文搜索查询,请参阅

SELECT ... WHERE MATCH('this')

SphinxQL语句

  • 布尔匹配运算符(隐式AND,显式OR,NOT和方括号),如 (one two) | (three !four)
  • 布尔匹配优化,看到OPTION boolean_simplify=1SELECT声明
  • 高级文本匹配运算符
    • 现场的限制,@title hello world@!title hello@(title,body) any of the two
    • 现场位置限制 @title[50] hello
    • MAYBE运算符,用于可选的关键字匹配, cat MAYBE dog
    • 词组匹配, "roses are red"
    • 仲裁匹配, "pick any 3 keywords out of this entire set"/3
    • 接近匹配,"within 10 positions all terms in yoda order"~10hello NEAR/3 world NEAR/4 "my test"
    • 严格的订单匹配, (bag of words) << "exact phrase" << this|that
    • 句子匹配 all SENTENCE words SENTENCE "in one sentence"
    • 段落匹配 "Bill Gates" PARAGRAPH "Steve Jobs"
    • 区域和区域跨度匹配,ZONE:(h3,h4) in any of these title tags以及ZONESPAN:(h2) only in a single instance
  • 关键字修饰符(通常可以在运算符中使用)
    • 精确(形态前)形式修饰符, raining =cats and =dogs
    • 字段开始和字段结束修饰符, ^hello world$
    • IDF(排名)提升, boosted^1.234
  • 子串和通配符搜索

    • 查看min_prefix_lenmin_infix_len指令
    • 使用th?se three keyword% wild*cards *verywher*?精确地= 1个字符;%= 0或1个字符;*= 0或多个字符)

TODO:描述更多,添加链接!

入门#

现在,这应该很简单。无需魔术安装!在任何平台上,充分要做的是:

  1. 获取二进制文件。
  2. searchd
  3. 创建索引。
  4. 运行查询。

参见下面的更多详细信息(在无配置模式下运行)。

或者,您可以使用以下indexer工具离线ETL数据:

  1. 获取二进制文件。
  2. 创建sphinx.conf,至少包含1个index部分。
  3. 运行indexer --all一次,以最初创建索引。
  4. searchd
  5. 运行查询。
  6. indexer --all --rotate定期运行,以更新索引。

请注意,该indexer工具不是将数据在线插入索引中,而是离线创建指定索引的卷影副本,然后发送信号searchd以选择该副本。因此,您永远都不应使用填充部分索引indexer;它总是全有或全无。

下面还提供了有关使用配置运行的更多详细信息,请参阅“编写第一个配置”部分。

Linux(和MacOS)入门#

版本和文件名有所不同,你很可能想配置狮身人面像至少有一点,但立即快速入门:

$ wget -q http://sphinxsearch.com/files/sphinx-3.0.2-2592786-linux-amd64.tar.gz
$ tar zxf sphinx-3.0.2-2592786-linux-amd64.tar.gz
$ cd sphinx-3.0.2-2592786-linux-amd64/bin
$ ./searchd
Sphinx 3.0.2 (commit 2592786)
Copyright (c) 2001-2018, Andrew Aksyonoff
Copyright (c) 2008-2016, Sphinx Technologies Inc (http://sphinxsearch.com)

listening on all interfaces, port=9312
listening on all interfaces, port=9306
WARNING: No extra index definitions found in data folder
$

而已; 守护程序现在应该正在运行并接受端口9306上的连接。您可以使用MySQL CLI连接到它(有关更多详细信息,请参见下文,或立即尝试mysql -P9306)。

Windows入门#

除了Windows searchd不会自动进入后台之外,几乎是相同的故事:

C:\Sphinx\>searchd.exe
Sphinx 3.0-dev (c3c241f)
...
accepting connections
prereaded 0 indexes in 0.000 sec

没关系 不要杀死它。只需切换到单独的会话并开始查询。

通过MySQL Shell运行查询#

运行MySQL CLI并将其指向端口9306。例如,在Windows上:

C:\>mysql -h127.0.0.1 -P9306
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 3.0-dev (c3c241f)
...

127.0.0.1在此示例中有意使用了两个原因(均由MySQL CLI怪癖引起,而不是Sphinx引起):

  • 有时,使用-P9306交换机需要IP地址,而不是localhost
  • 有时localhost可以工作,但会导致连接延迟

但即使在最简单的情况下,也mysql -P9306应该可以正常工作。

然后从那里运行SphinxQL查询:

mysql> CREATE TABLE test (gid integer, title field stored,
    -> content field stored);
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO test (id, title) VALUES (123, 'hello world');
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO test (id, gid, content) VALUES (234, 345, 'empty title');
Query OK, 1 row affected (0.00 sec)

mysql> SELECT * FROM test;
+------+------+-------------+-------------+
| id   | gid  | title       | content     |
+------+------+-------------+-------------+
|  123 |    0 | hello world |             |
|  234 |  345 |             | empty title |
+------+------+-------------+-------------+
2 rows in set (0.00 sec)

mysql> SELECT * FROM test WHERE MATCH('hello');
+------+------+-------------+---------+
| id   | gid  | title       | content |
+------+------+-------------+---------+
|  123 |    0 | hello world |         |
+------+------+-------------+---------+
1 row in set (0.00 sec)

mysql> SELECT * FROM test WHERE MATCH('@content hello');
Empty set (0.00 sec)

SphinxQL是我们自己的SQL方言,在相应的SphinxQL参考部分中有更详细的描述。

编写第一个配置#

上面所有这些简单的示例都使用了无配置模式,该模式searchd存储和管理所有数据和设置./sphinxdata数据文件夹,而您必须自己管理所有内容searchd

但是,由于从v.3.x开始,无配置模式尚未完成(某些设置和工具仍然缺失),因此许多实例可能仍希望通过旧的sphinx.conf配置文件指定其索引和设置。

您可以从etc/sphinx-min.conf.dist示例开始。该示例说明了如何同时使用您将即时填充的RT索引和将从数据库中填充的纯索引。对于后者,还有一个很小的匹配etc/example.sqlMySQL转储,其中包含一些示例表和test1索引可以从中sphinx-min.conf.dist访问的行。

但是,即使是很小的样本也并非真正少。最低配置仅要求您为日志和pid文件指定2个路径,并定义1个索引:

searchd
{
    log = ./data/searchd.log
    pid_file = ./data/searchd.pid
}

index mydocs
{
    type = rt
    path = ./data/mydocs
    rt_field = title
    rt_attr_json = j
}

这样,您就可以searchd在配置模式下启动并立即启动查询。(data尽管不要忘记创建目录。)

$ mkdir data

$ ./searchd
Sphinx 3.3.1-dev (commit 00642f6c)
Copyright (c) 2001-2020, Andrew Aksyonoff
Copyright (c) 2008-2016, Sphinx Technologies Inc (http://sphinxsearch.com)

using config file './sphinx.conf'...
listening on all interfaces, port=9306
precaching index 'mydocs'
precached 1 indexes using 1 threads in 0.0 sec
$ mysql -h127.0.0.1 -P9306
mysql> insert into mydocs values(111, 'hello world', '{"foo":"bar"}');
Query OK, 1 row affected (0.01 sec)

mysql> select * from mydocs where match('hello');
+------+---------------+
| id   | j             |
+------+---------------+
|  111 | {"foo":"bar"} |
+------+---------------+
1 row in set (0.00 sec)

从PHP,Python等运行查询#

<?php

$conn = mysqli_connect("127.0.0.1:9306", "", "", "");
if (mysqli_connect_errno())
    die("failed to connect to Sphinx: " . mysqli_connect_error());

$res = mysqli_query($conn, "SHOW VARIABLES");
while ($row = mysqli_fetch_row($res))
    print "$row[0]: $row[1]\n";

TODO:示例

通过HTTP运行查询#

TODO:示例

indexer在Linux上安装SQL驱动程序#

这仅影响indexerETL工具。如果您从不使用SQL源从SQL源批量加载数据(当然CSV和XML源仍然可以),则可以安全地跳过此部分。(在Windows上,所有驱动程序都随软件包一起提供。)

根据您的操作系统,所需的软件包名称可能会有所不同。以下是Ubuntu和CentOS的一些最新名称(截至2018年3月):

ubuntu$ apt-get install libmysqlclient-dev libpq-dev unixodbc-dev
ubuntu$ apt-get install libmariadb-client-lgpl-dev-compat

centos$ yum install mariadb-devel postgresql-devel unixODBC-devel

为什么需要这些,以及它们如何工作?

indexer本机支持MySQL(或MariaDB),PostgreSQL和UnixODBC驱动程序。这意味着它可以本地连接到这些数据库,运行SQL查询,提取结果并从中创建全文索引。现在,二进制文件始终带有启用的支持

但是,您仍然需要在系统上安装特定的驱动程序,以便indexer可以动态加载它并访问数据库。根据您使用的特定数据库和操作系统,程序包名称可能有所不同,但是这里有一些常用的指针。

驱动程序库按名称加载。尝试使用以下名称:

  • MySQL:libmysqlclient.solibmariadb.so
  • PostgreSQL: libpq.so
  • ODBC: libodbc.so

为了支持MacOS,还尝试了.dylib扩展名(除了.so)。

主要概念#

las,许多项目倾向于重塑自己的词典,Sphinx也不例外。有时,这可能会在没有明显原因的情况下造成混乱。首先,SQL专家称其为“表”(如果它们足够大以至于可以记住埃德加·科德,则称其为“关系”),而MongoDB专家则称其为“集合”,而我们的文本搜索专家则倾向于称其为“索引”,而不是真正的“索引”。搞的恶作剧和恶意下去,但只是因为对我们来说,这些东西都是主要FT(全文)索引。值得庆幸的是,大多数概念都足够接近,因此我们的个人Sphinx小词典很小。让我们来看看。

简短的备忘单!

狮身人面像 最接近的SQL等效项
指数
文件
栏位或属性 列和/或FULLTEXT索引
索引字段 只是文本列上的FULLTEXT索引
储存栏位 文本列及其上的FULLTEXT索引
属性
MVA 具有INT_SET类型的列
JSON属性 具有JSON类型的列
属性索引 指数
文件编号,docid 名为“ id”的列,其类型为BIGINT
行ID,行ID 内部狮身人面像的行号

现在,进行更详细的解释。

指标#

Sphinx索引是文档的半结构化集合。它们看起来更像SQL表而不是Mongo集合,但从本质上讲,它们两者都不是。这里的主要基础数据结构是全文索引。这是一种特殊的结构,它使我们能够快速响应查询,例如“给我所有提及This或That关键字的文档的(内部)标识符”。Sphinx提供的所有其他内容(任何其他属性,文档存储,甚至SQL或HTTP查询方言,等等)本质上都是在该基本数据结构的基础上进行的一种添加。好吧,因此就是“索引”名称。

在模式方面,Sphinx索引尝试结合最佳的模式化和无模式世界。对于预先知道类型的“列”,可以使用静态类型的属性,并获得绝对的效率。对于更多动态数据,您可以将其全部放入JSON属性中,并且仍然可以获得相当不错的性能。

因此,从某种意义上说,Sphinx索引== SQL表,除了(a)全文搜索速度快,并且具有许多针对全文搜索的调整选项;(b)JSON“列”(属性)是本机支持的,因此您可以无模式运行;(c)对于全文索引字段,您可以选择存储全文索引并放弃原始值。

文件资料#

文档本质上只是命名文本字段和任意类型的属性的列表。非常类似于SQL行;实际上几乎没有区别。

从v.3.0.1开始,Sphinx仍然需要唯一的id属性,并隐式将一id BIGINT列注入索引(如您在“ 入门”部分可能会注意到的)。我们仍然使用这些docid来标识in DELETE和其他语句中的特定行。但是,与v.2.x不同,我们不再使用docid在内部标识文档。因此,已经允许零和负docid。

领域#

字段是Sphinx索引的文本并使关键字可搜索。他们总是被收录,如建立全文索引。它们的原始未索引内容也可以存储到索引中,以供以后检索。默认情况下,它们不是,Sphinx将仅返回属性,而不返回内容。但是,如果您明确地将它们标记为已存储(使用伪指令在ETL配置文件storedCREATE TABLE或ETL配置文件中使用标志stored_fields),则也可以取回字段:

mysql> CREATE TABLE test1 (title field);
mysql> INSERT INTO test1 VALUES (123, 'hello');
mysql> SELECT * FROM test1 WHERE MATCH('hello');
+------+
| id   |
+------+
|  123 |
+------+
1 row in set (0.00 sec)

mysql> CREATE TABLE test2 (title field stored);
mysql> INSERT INTO test2 VALUES (123, 'hello');
mysql> SELECT * FROM test2 WHERE MATCH('hello');
+------+-------+
| id   | title |
+------+-------+
|  123 | hello |
+------+-------+
1 row in set (0.00 sec)

存储的字段内容存储在称为文档存储或DocStore的特殊索引组件中。

属性#

Sphinx支持以下属性类型:

  • 整数,无符号32位整数
  • BIGINT,带符号的64位整数
  • FLOAT,32位(单精度)浮点
  • BOOL,1位布尔值
  • STRING,文本字符串
  • JSON,JSON文档
  • MVA,一组不区分顺序的唯一INTEGER
  • MVA64,一组不区分顺序的唯一BIGINT

所有这些都应该非常简单。但是,有一些Sphinx特定的JSON性能技巧值得一提:

  • 所有标量值(整数,浮点数,双精度数)都将转换并本地内部存储。
  • 所有标量值数组都将被检测到,并在本地内部存储。
  • 您可以使用123.45f语法扩展来标记32位浮点数(默认情况下,JSON中的所有浮点值都是64位双精度数)。

例如,将以下文档存储在Sphinx的JSON列中时:

{"title":"test", "year":2017, "tags":[13,8,5,1,2,3]}

Sphinx检测到“标签”数组仅包含整数,并使用24个字节精确地存储数组数据,六个值中的每一个仅使用4个字节。当然,存储JSON密钥和常规文档结构仍然有开销,因此整个文档将花费更多。不过,当要将大量数据存储到Sphinx索引中以备后用时,只需提供一个一致类型的JSON数组,该数据将被存储-并进行处理!-效率最高。

属性应该适合RAM,Sphinx针对这种情况进行了优化。当然,理想情况下,理想情况下,所有索引数据都应适合RAM,同时由足够快的SSD支持以实现持久性。

现在,受支持的类型中有定宽变宽属性。自然,标量(例如INTEGER和FLOAT)将始终始终恰好占据4个字节,而STRING和JSON类型则可以短至为空。或长达几兆字节。内部如何运作?换句话说,为什么我不只是将所有内容另存为JSON?

答案是性能。在内部,Sphinx对于这些行部分有两个单独的存储。固定宽度属性(包括隐藏的系统属性)本质上存储在大型静态NxM矩阵中,其中N是行数,M是固定宽度属性的数。对它们的任何访问都非常快。一行的所有可变宽度属性都分组在一起,并存储在单独的存储中。到第二个存储(或“ vrow”存储,是“可变宽度行部分”存储的简称)的单个偏移量被存储为隐藏的固定宽度属性。因此,如您所见,访问字符串或JSON或MVA值,更不用说JSON密钥了,访问起来会更加复杂。例如,year要从以上示例访问该JSON密钥,Sphinx将需要:

  • vrow_offset从隐藏的整数属性读取
  • 使用该偏移量访问vrow部分
  • 解码vrow,并找到所需的JSON属性开始
  • 解码JSON,然后找到year密钥开始
  • 检查密钥类型,以防万一需要将其转换为整数
  • 最后,读取year

当然,这里的每个步骤都进行了优化,但是,如果您访问很多这些值(用于对查询结果进行排序或过滤),则会对性能产生影响。同样,密钥被深深地嵌入到该JSON中,更糟。例如,使用具有1,000,000行和仅4个整数属性以及JSON中存储的完全相同的4个值的微小测试,计算总和将得出以下结果:

属性 时间 慢一点
任何INT 0.032秒 --
第一个JSON密钥 0.045秒 1.4倍
第二个JSON密钥 0.052秒 1.6倍
第三个JSON密钥 0.059秒 1.8倍
第四个JSON密钥 0.065秒 2.0倍

有了更多的属性,它最终的速度甚至会降低甚至超过2倍,特别是如果我们还抛出更复杂的属性(例如字符串或嵌套对象)的话。

因此,最重要的是,为什么不使用JSON呢?只要您的查询每个仅触及几行,实际上就可以了!但是,如果您有大量数据,则应尝试为查询标识一些“最繁忙”的列,并将它们存储为“常规”类型的列,这在一定程度上提高了性能。

使用DocStore#

将字段存储到索引中很容易,只需在stored_fields指令中列出这些字段,就可以设置好了:

index mytest
{
    type = rt
    path = data/mytest

    rt_field = title
    rt_field = content
    stored_fields = title, content
    # hl_fields = title, content

    rt_attr_uint = gid
}

让我们检查一下它是如何工作的:

mysql> desc mytest;
+---------+--------+-----------------+------+
| Field   | Type   | Properties      | Key  |
+---------+--------+-----------------+------+
| id      | bigint |                 |      |
| title   | field  | indexed, stored |      |
| content | field  | indexed, stored |      |
| gid     | uint   |                 |      |
+---------+--------+-----------------+------+
4 rows in set (0.00 sec)

mysql> insert into mytest (id, title) values (123, 'hello world');
Query OK, 1 row affected (0.00 sec)

mysql> select * from mytest where match('hello');
+------+------+-------------+---------+
| id   | gid  | title       | content |
+------+------+-------------+---------+
|  123 |    0 | hello world |         |
+------+------+-------------+---------+
1 row in set (0.00 sec)

是的,原始文件内容!一般而言,这不是一个巨大的步骤,无论如何对于数据库而言都不是;但是对Sphinx来说是一个不错的改进,它最初是为“仅用于搜索”而设计的(哦,年轻人的错误)。DocStore可以做的还不止这些,即:

  • 存储索引字段,store_fields指令
  • 存储未索引字段,stored_only_fields指令
  • 将预先计算的数据存储到加速片段中,hl_fields指令
  • 要微调了一下,使用docstore_typedocstore_compdocstore_block指令

因此DocStore可以有效地替换现有的rt_attr_string指令。有什么区别,以及何时使用它们?

rt_attr_string创建一个未压缩的属性,并将其存储在RAM中。属性应该很小,并且适合数以百万计的筛选(WHERE),排序(ORDER BY)和其他类似操作。因此,如果您确实需要运行诸如... WHERE title ='abc'之类的查询,或者万一要动态更新这些字符串,则仍然需要属性。

但是用这种方式很少能访问完整的原始文档内容!取而代之的是,您通常只需要少数几个(大约10s至100s),即可将它们显示在最终搜索结果中和/或创建摘要。DocStore正是为此目的而设计的。它压缩接收到的所有数据(默认情况下),并尝试将大多数结果“归档”保留在磁盘上,最后一次只获取几个文档。

片段在DocStore中变得非常有趣。您可以分别从特定的存储字段,整个文档或子文档生成摘要:

SELECT id, SNIPPET(title, QUERY()) FROM mytest WHERE MATCH('hello')
SELECT id, SNIPPET(DOCUMENT(), QUERY()) FROM mytest WHERE MATCH('hello')
SELECT id, SNIPPET(DOCUMENT({title}), QUERY()) FROM mytest WHERE MATCH('hello')

使用hl_fields可加速高亮如果可能的话,有时会使得片段的时间更快。如果您的文档足够大(例如,比推文大一点),请尝试一下!没有hl_fields,SNIPPET()函数将每次必须重新解析文档内容。有了它,就可以将解析的表示形式压缩并存储到索引中,从而以不小的数量的CPU工作量来换取更多的磁盘空间和一些额外的磁盘读取。

说到磁盘空间与CPU权衡,这些调整旋钮可让您针对特定索引微调DocStore:

  • docstore_type = vblock_solid (默认)将小文档分组到单个压缩块中,并达到给定限制:压缩效果更好,访问速度更慢
  • docstore_type = vblock 分开存储每个文档:更差的压缩,更快的访问
  • docstore_block = 16k (默认)可让您调整块大小限制
  • docstore_comp = lz4hc (默认)使用LZ4HC算法进行压缩:压缩效果更好,但速度较慢
  • docstore_comp = lz4 使用LZ4算法:压缩效果较差,但速度更快
  • docstore_comp = none 禁用压缩

使用属性索引#

快速启动:我们现在有CREATE INDEX声明,有时确实可以使您的查询更快!

CREATE INDEX i1 ON mytest(group_id)
DESC mytest
SELECT * FROM mytest WHERE group_id=1
SELECT * FROM mytest WHERE group_id BETWEEN 10 and 20
SELECT * FROM mytest WHERE MATCH('hello world') AND group_id=23
DROP INDEX i1 ON mytest

点读取,范围读取MATCH()以及索引读取与索引读取之间的交集都可以使用。而且,GEODIST()还可以自动使用索引(请参阅下文)。目标之一是完全消除在索引中插入“伪关键字”的需求。(此外,与索引文本相反,可以动态更新属性索引。)

JSON键上的索引也应该起作用,但是在创建索引时可能需要将它们转换为特定类型:

CREATE INDEX j1 ON mytest(j.group_id)
CREATE INDEX j2 ON mytest(INTEGER(j.year))
CREATE INDEX j3 ON mytest(FLOAT(j.latitude))

从v.3.0开始,只能在RT索引上创建属性索引。但是,您可以(几乎)使用,将您的纯索引立即转换为RT ATTACH ... WITH TRUNCATE,然后运行CREATE INDEX,如下所示:

ATTACH INDEX myplain TO myrt WITH TRUNCATE
CREATE INDEX date_added ON myrt(date_added)

与Geosearches GEODIST()能也受益颇多,从属性索引。他们可以自动计算围绕静态参考点的一个或多个边界框,然后使用索引读取仅处理一部分数据。有关更多详细信息,请参阅地理搜索部分

TODO:描述更多!

查询优化器和索引提示#

查询优化器是一种基于每个查询来决定是使用还是忽略特定索引来计算当前查询的机制。

优化器通常可以选择任何适用索引的任意组合。根据成本估算选择特定的索引组合。奇怪的是,即使只有两个索引,这种选择也不是完全显而易见的。

例如,假设我们正在进行地理搜索,如下所示:

SELECT ... FROM test1
WHERE (lat BETWEEN 53.23 AND 53.42) AND (lon BETWEEN -6.45 AND -6.05)

假设我们在latlon列上都有索引,并且可以使用它们。此外,我们可以从该索引对中获得确切的最终结果集,而无需任何额外的检查。但是我们应该吗?有时不使用两个索引,而是有时仅使用一个索引更有效!因为有了2个索引,我们必须:

  1. 执行lat范围索引读取,获取X个lat候选rowid
  2. 执行lon范围索引读取,获取Y个lon候选rowid
  3. 与X和Y的rowid相交,获得N个匹配的rowid
  4. 查找N个结果行
  5. 处理N个结果行

当使用1索引时,lat我们只需要:

  1. 执行lat范围索引读取,获取X个lat候选rowid
  2. 查找X个候选行
  3. lon范围进行X次检查,获得N个匹配行
  4. 处理N个结果行

现在,latlon经常有些相关。这意味着X,Y和N值都可以非常接近。例如,假设在该特定纬度范围内有11K个匹配项,在经度范围内有12K个匹配项,即10K个最终匹配项。X = 11000, Y = 12000, N = 10000。然后仅使用1个索引就意味着我们可以避免读取12K的latrowid,然后再相交23k的rowid,但是引入了2K的额外行查找和12K的lon检查。猜猜什么,行查找和额外的检查实际上是更便宜的操作,而我们所做的却更少。因此,通过一些快速估算,突然发现在2个适用的索引中仅使用1个索引似乎是更好的选择。确实可以在实际查询中确认这一点。

这正是优化器的工作方式。基本上,它检查多个可能的索引组合,尝试估计相关的查询成本,然后选择找到的最佳索引。

但是,可能组合的数量随着属性索引计数的爆炸性增长。考虑一个非常疯狂(但可能)的案例,其中包含多达20个适用的索引。这意味着超过一百万种可能的“开/关”组合。即使对它们全部进行快速估算也将花费太多时间。优化器中有内部限制来防止这种情况。反过来,这意味着最终可能不会选择某些“理想”的索引集。(但是,当然,这是一种罕见的情况。通常只有几个适用的索引,例如1到10,因此优化程序可以提供“强行强制”多达1024种可能的索引组合,并且这样做)。

现在,甚至更糟的是,数量和成本估算都只是这样。仅估计。它们可能会稍微偏离或偏离。实际查询费用可能与我们执行查询时估算的费用有所不同。

由于这些原因,优化器可能偶尔会选择次优的查询计划。在这种情况下,或者可能只是出于测试目的,您可以使用SELECT提示来调整其行为,并使其强制使用或忽略特定的属性索引。有关确切语法和行为的参考,请参阅“索引提示子句”

使用k批次#

使用K批处理(“杀害批处理”),可以在将新数据批量加载到Sphinx中时批量删除旧版本的文档(行),例如,在较旧的主存档索引之上添加新的增量索引。

Sphinx v.3.x中的K-batches替换了v.2.x及更早版本中的k-lists(“ kill list”)。主要区别在于:

  1. 他们是不是匿名了。
  2. 现在,它们仅在加载时应用一次。(与每次搜索相反,糟糕)。

“非匿名”表示在将具有相关k批处理的新索引加载到时searchd您必须显式指定应该从中删除行的目标索引。换句话说,“增量”现在必须在索引时明确指定他们要从中删除旧文档的所有“主要”索引。

应用k批处理的效果等同于DELETE FROM X WHERE id=Y针对kbatch指令中列出的每个索引X 和存储在k批处理中的每个文档ID Y (一次)查询一堆。通过索引格式更新,现在即使在“普通”索引中也可以实现,而且效率也很高。

K批处理仅应用一次。成功应用于所有目标索引后,该批将被清除。

因此,例如,当您加载delta具有以下设置的索引时:

index delta
{
    ...
    sql_query_kbatch = SELECT 12 UNION SELECT 13 UNION SELECT 14
    kbatch = main1, main2
}

通常会发生以下情况:

  • delta

kbatch文件已加载

  • 在此示例中,它将具有3个文档ID:12、13和14

  • 具有这些ID的文档将从中删除 main1

  • 具有这些ID的文档将从中删除 main2

  • main1main2将这些删除内容保存到磁盘

  • 如果一切顺利,delta将清除kbatch文件

所有这些操作都非常快,因为现在使用位图在内部实现了删除。因此,通过id删除给定的文档会导致哈希查找和一些翻转。简而言之,非常快。

“加载”可以通过重新启动或旋转或其他方式发生,k批处理仍应尝试自行应用。

最后但并非最不重要的一点是,您还可以使用kbatch_source避免将所有新添加的文档ID显式存储到k批处理中,而可以使用kbatch_source = kl, id或just kbatch_source = id;这会将索引中的所有文档ID自动添加到其k批处理中。默认值为kbatch_source = kl,即仅使用显式提供的docid。

进行批量数据加载#

TODO:描述旋转(旧式),RELOAD,ATTACH等。

使用JSON#

在大多数情况下,在Sphinx中使用JSON应该非常简单。您只需在适当的列(即属性)中放置几乎任意的JSON。然后,您只需使用col1.key1.subkey2.subkey3语法即可访问必要的键。或者,您使用col1.key1[123]语法访问数组值。就是这样。

对于30秒的启动时间,您可以像下面这样配置测试RT索引:

index jsontest
{
    type = rt
    path = data/jsontest
    rt_field = title
    rt_attr_json = j
}

然后重新启动或searchd重新加载配置,并触发一些测试查询:

mysql> INSERT INTO jsontest (id, j) VALUES (1, '{"foo":"bar", "year":2019,
  "arr":[1,2,3,"yarr"], "address":{"city":"Moscow", "country":"Russia"}}');
Query OK, 1 row affected (0.00 sec)

mysql> SELECT j.foo FROM jsontest;
+-------+
| j.foo |
+-------+
| bar   |
+-------+
1 row in set (0.00 sec)

mysql> SELECT j.year+10, j.arr[3], j.address.city FROM jsontest;
+-----------+----------+----------------+
| j.year+10 | j.arr[3] | j.address.city |
+-----------+----------+----------------+
|    2029.0 | yarr     | Moscow         |
+-----------+----------+----------------+
1 row in set (0.00 sec)

但是,有时这还不够(主要是出于性能方面的考虑),因此我们既有几个特定于Sphinx的JSON语法扩展,也有几个重要的内部实现细节需要讨论,包括一些特定于Sphinx的限制。简而言之,如下:

  • 优化的标量存储(为int8int32int64boolfloat,和NULL类型)
  • 优化阵列存储(为int8int32int64floatdouble,和string类型)
  • 通过键压缩优化键名存储(可选,带json_packed_keys = 1指令)
  • 0.0f 32位浮点值的语法扩展
  • int8[]float[]8位整数和32位浮点数组的语法扩展

优化的存储意味着Sphinx 通常会自动检测独立值和数组的实际值类型,然后使用最小的可用存储类型。

因此,当一个32位(4字节)整数足以容纳一个数值时,Sphinx会自动存储该值。如果溢出,不用担心,Sphinx会自动切换到8字节整数值,甚至是double值(仍然是8字节)。

同上数组。当数组包含实际类型的混合时,Sphinx会很好地处理它,并存储一个通用数组,其中每个元素都附加有不同的类型。但是,当您的数组实际上仅包含一个非常特定的类型(例如,仅常规32位整数)时,Sphinx会自动检测到该事实,并以优化的方式存储数组,每个值仅使用4个字节,并跳过重复的类型。所有内置函数均支持所有此类优化的数组类型,并具有特殊的快速代码路径以透明方式处理它们。

作为v.3.2的,这可能会优化的方式排列的值类型int8int32int64floatdouble,和string。这几乎涵盖了所有常规的数值类型,因此,您要做的所有确保优化启动的工作都是,仅在数据中使用一种实际的类型。

因此,大多数情况下,一切都是自动驾驶。但是,该规则有几个例外,仍然需要您一点点的努力!

首先,与floatvs double类型有关。Sphinx中浮点值的默认存储类型是高精度的64位double类型,就像JSON指定的那样。

因此常规{"scale":1.23}语法将使Sphinx存储一个8字节的double值。但是,对于某些(如果不是大多数!)任务来说,这可能会显得过于精确,并且为了节省存储空间,您可能想使用32位(4字节)float类型存储值。

不幸的是,目前尚无标准的方法来指定,因此Sphinx提供了一个自定义扩展:将f后缀附加到您的值上。请改用{"scale":1.23f}语法。这在Sphinx中有效,并且让它知道一个float类型就足够了。

其次,int8数组必须是显式的。尽管斯芬克斯可以自动检测的事实,所有的数组值在-128到127范围内的整数,并能有效地利用每值只有1字节存储,但它只是做这样的假设,并且使用int32类型来代替。

之所以会发生这种情况,是因为Sphinx无法通过查看这些值来判断您是否真的想要优化的int8向量,还是打算仅使用一个占位符(用0,或-1,或您拥有什么)int32来表示未来的向量更新。鉴于JSON更新当前已就位,在此决策点,Sphinx选择采用更保守但更灵活的路线,并为int32甚至可以更有效地存储的内容存储向量[0, 0, 0, 0]

要强制将向量转换为1字节的超薄值,必须使用语法扩展名并将其int8[0, 0, 0, 0]用作值。

第三,当心整数与浮点混合。自动检测是基于每个值进行的。意思是将数组值like [1, 2, 3.0]标记为混合两种不同类型,int32double。因此,对于这个特定的阵列,int32nor和(更糟糕的)double阵列存储优化都无法启动。

您可以在此处使用常规JSON语法在Sphinx上强制使用任何JSON标准类型。要将其存储为整数,您应该简单地摆脱触发双打的讨厌点,并使用[1, 2, 3]语法。相反,对于双打,点应无处不在,即。您应该使用[1.0, 2.0, 3.0]语法。

最后,对于非标准float类型扩展,也可以使用f后缀,即。[1.0f, 2.0f, 3.0f]句法。但这可能不方便,因此您也可以使用float[1, 2, 3.0]语法。这两种形式中的任何一种都使Sphinx能够将向量自动转换为漂亮且快速优化的浮点数。

这就是所有的价值观。那钥匙呢?

通常,密钥按原样存储。这意味着,如果您superLongKey(几乎)每个文档中都有一个,则该键将存储为纯旧的文本字符串,并重复与文档相同的次数。所有这些重复将消耗一些RAM字节。灵活,但效率不高。

因此,经验法则是,超长键名虽然很好,但并不是很好。与常规JSON一样。当然,对于较小的索引,节省的费用可能微不足道。但是对于较大的密钥,您可能需要考虑使用较短的密钥名称。

或者,打包的JSON密钥功能会自动缓解这种情况。它跟踪最常提及的键,并使用每个键值仅使用1个字节来存储顶部键。您可以通过json_packed_keys = 1在索引配置中设置标志来启用它(并在必要时重建索引)。这样,您可以根据需要使用尽可能长的键名,Sphinx会在内部有点“压缩” JSON。

但是要当心并进行基准测试,目前在索引编制和搜索时,有时可能会在此处产生一些相关的性能影响(即使不是很大)。(否则,包装将始终保持打开状态!)

JSON比较怪癖#

在值类型方面,与JSON进行比较可能会有些棘手。由于所有integervs floatvs double爵士乐,尤其是数字音乐。(并且,请注意,默认情况下,浮点值将存储为double。)简要地,请注意:

  1. 字符串比较严格,需要字符串类型。

这意味着WHERE j.str1='abc'只有在满足以下所有条件时才必须通过检查:1)str1密钥存在;2)str1值类型完全相同string;3)值匹配。

因此,对于与字符串常量比较的突然数值(例如,{"str1":123}WHERE j.str1='123'条件比较的值),检查将失败。应当,这里没有隐式转换。

  1. 与整数的数值比较可以匹配任何数值类型,而不仅仅是整数。

意味着{"key1":123}{"key1":123.0}值都必须通过WHERE j.key1=123检查。再次如预期。

  1. 对浮点数的数字比较会强制将双精度值转换为(单精度)浮点数,并且可能会出现舍入问题。

这意味着当您将类似的内容存储{"key1":123.0000001}到索引中时,WHERE j.key1=123.0检查将通过,因为舍入将float丢失小数部分。但是,同时WHERE j.key1=123检查不会通过,因为检查将使用原始的double值并将其与整数常量进行比较。

这可能是一个有点混乱,但在其他方面(不四舍五入),情况会更糟的争论:在一个更加反直觉的方式,{"key1":2.22}没有通过WHERE j.key1>=2.22检查,因为这里的基准定为float(2.22),并四舍五入的话,由于,double(2.22) < float(2.22)

待办事项:描述限制,json_xxx设置,我们的语法扩展等。

使用数组属性#

数组属性使您可以将固定数量的整数或浮点值保存到索引中。支持的类型为:

  • attr_int_array 存储带符号的32位整数;
  • attr_int8_array 存储带符号的8位整数(-128至127范围);
  • attr_float_array 存储32位浮点数。

要声明数组属性,请使用以下语法:

{rt|sql|xmlpipe|csvpipe|tsvpipe}_attr_{int|int8|float}_array = NAME[SIZE]

where NAME是属性名称,并且SIZE是元素中的数组大小。例如:

index rt
{
    type = rt

    rt_field = title
    rt_field = content

    rt_attr_uint = gid # regular attribute
    rt_attr_float_array = vec1[5] # 5D array of floats
    rt_attr_int8_array = vec2[3] # 3D array of small 8-bit integers
    # ...
}

source test1
{
    type = mysql

    sql_attr_int8_array = vec1[17] # 17D array of small 8-bit integers
    # ...
}

阵列尺寸必须在2到8192之间(含2和8192)。

数组将对齐到最接近的4个字节。这意味着int8_array具有17个元素的实际将使用20个字节进行存储。

INSERT查询和源索引的预期输入数组值是带有值的逗号或空格分隔的字符串,或者为空字符串:

INSERT INTO rt (id, vec1) VALUES (123, '3.14, -1, 2.718, 2019, 100500');
INSERT INTO rt (id, vec1) VALUES (124, '');

空字符串将对数组进行零填充。非空字符串必须经过严格验证。首先,必须有尽可能多的值可以容纳数组。因此,您不能将3或7个值存储到5个元素的数组中。其次,值范围也可能得到验证。所以,你将不能够的值存储1000到一个int8_array,因为它的-128..127超出范围。

尝试INSERT使用无效的数组值将失败。例如:

mysql> INSERT INTO rt (id, vec1) VALUES (200, '1 2 3');
ERROR 1064 (42000): bad array value

mysql> INSERT INTO rt (id, vec1) VALUES (200, '1 2 3 4 5 6');
ERROR 1064 (42000): bad array value

mysql> INSERT INTO rt (id, vec2) VALUES (200, '0, 1, 2345');
ERROR 1064 (42000): bad array value

但是,当使用进行批量索引编制时indexer,将报告无效的数组值作为警告,并对该数组进行零填充,但它不会使整个索引编制批次失败。

目前,唯一支持数组的函数是DOT(),因此您可以计算数组和常数向量之间的点积:

mysql> SELECT id, DOT(vec1,FVEC(1,2,3,4,5)) d FROM rt;
+------+--------------+
| id   | d            |
+------+--------------+
|  123 | 510585.28125 |
|  124 |            0 |
+------+--------------+
2 rows in set (0.00 sec)

使用字形#

Sphinx支持几种不同的关键字重新映射类型(通常在索引编制和搜索时都应用),所有这些当前都称为单词形式。主要类型如下。

  1. 形态替代1:1字形。
  2. 形态前的M:N字形。
  3. 后形态1:1字形。

此外,这些类型中的两种只能在索引时应用。

  1. 仅限文档的形态-替换1:1字形。
  2. 仅文档的预形态M:N字形。

Wordform通常都在单独的文本文件(或一组文件)中列出,并使用wordforms指令添加到索引中。所有这些类型的wordforms文件语法如下。

geese => goose              # type 1, morph-repl 1:1
core 2 duo => c2d           # type 2, pre-morph M:N
e8400 => c2d e8400          # type 2, pre-morph M:N
~vehicle => car             # type 3, post-morph 1:1
!foo => bar                 # type 4, doc-only morph-repl 1:1
!grrm => grrm george martin # type 5, doc-only pre-morph M:N

因此,一般而言,字形只是关键字的两个列表(分别是源映射和目标映射),由=>特殊标记分隔。

后形态单词形式~在第一列中标记为。

仅文档的字形!在第一列中用标记。

注释用标记#,并且从#一行到结尾的所有内容都被视为注释。

简而言之,所有这些不同类型的工作方式如下。

有效地替换形态的字形是最优先的形态选择。它们先于任何形态发生,并完全替代任何其他形态处理。因此,在启用任何其他形态处理后,请格外小心(最好还是避免使用它们)。

例如,当您使用时,geese => goose上面的示例完全错误morphology = stem_en。不,它确实将复数形式正常化geese为单数形式goose,因此该部分是正确的。问题是,Porter词干分析器无法goose对其自身进行归一化,而是会发射出goos词干。因此,要专门修复Porter词干分析器,您必须映射到该词,即。使用geese => goos字形。极度照顾。

形态前字形在形态之前重新映射源关键字,然后将目标关键字另外传递通过形态。因此,例如,six geese => 6 goose不会受到stem_en问题上面,因为这两个6goose也将梗。

目前,只有M:N字形可以是预形态,而不是1:1,因此您必须具有多个源关键字或多个目标关键字。

后形态在词法之后重新映射源关键字。请注意,此刻(并且很不直观),目标关键字将不会stem_en再次通过形态传递,因此问题再次出现。

纯文档字形仅在索引编制时应用,而不会在查询时应用。这对于索引时间扩展非常有用,因此这就是带有示例的示例grrm也将其映射到自身的原因,而不仅仅是george martin

在“扩展”用例中,与类似的常规单词形式相比,它们在搜索时效率更高。

确实,当搜索源映射时,常规字词形式将扩展为所有关键字(在我们的示例中为所有3个关键字grrm george martin),获取并与它们相交,并且对所有内容都起作用……什么都没有!因为我们仅通过获取source关键字就可以更有效地获得完全相同的结果(就grrm在我们的示例中)。而这正是纯文档字形的工作方式,它们将完全跳过扩展。

搜索目标映射(的一部分)时,不会有任何变化。在那种情况下,仅文档形式和常规形式都将完全相同地执行查询。

最重要的是,在进行扩展时,请使用仅文档格式的字形,以避免不必要的性能损失。

使用UDF#

UDF概述#

Sphinx支持用户定义的函数(或简称UDF),可用于扩展其表达式引擎:

SELECT id, attr1, myudf(attr2, attr3+attr4) ...

您可以searchd动态地将UDF加载和卸载,即。无需重新启动守护程序本身,然后在搜索和排序时在大多数表达式中使用它们。UDF功能的快速摘要如下。

  • UDF可以接受Sphinx支持的大多数参数类型,即:
  • 数字,即。整数(32位和64位)和浮点数(32位);
  • MVA,即 整数集(32位和64位);
  • 字符串,包括二进制非ASCIIZ blob;
  • FACTORS(),即。具有等级信号的特殊斑点;
  • JSON对象,包括子对象或单个字段;
  • 浮点向量
  • UDF可以返回整数,浮点数或字符串值。
  • UDF可以在查询设置阶段检查参数编号,类型和名称,并引发错误。

UDF具有多种用途,例如:

  • 添加自定义数学或字符串函数;
  • 从Sphinx内部访问数据库或文件;
  • 实施复杂的排名功能。

UDF驻留在外部动态库(.soUNIX和.dllWindows系统上的文件)中。plugin_dir出于明显的安全原因,库文件需要驻留在指令指定的受信任文件夹中:保护单个文件夹很容易;让任何人安装任意代码searchd都存在风险。您可以分别searchd使用with CREATE FUNCTIONDROP FUNCTIONSphinxQL语句将它们动态地加载和卸载。另外,您可以使用RELOAD PLUGINS语句无缝地重新加载UDF(和其他插件)。Sphinx会跟踪当前加载的函数,也就是说,每次创建或删除UDF时,都会searchd将其状态sphinxql_state作为普通的老式SQL脚本写入文件中。

成功加载UDF后,就可以SELECT像任何内置函数一样在您的语句或其他语句中使用它:

SELECT id, MYCUSTOMFUNC(groupid, authorname), ... FROM myindex

多个UDF(和其他插件)可以驻留在单个库中。该库只会被加载一次。一旦删除所有UDF和插件,它将自动卸载。

尚不支持聚合功能。换句话说,您的UDF一次将仅被单个文档调用,并且将为该文档返回一些值。尚不可能编写一个可以计算总值的函数,例如AVG()在共享相同GROUP BY键的整个文档组中。但是,您可以在内置的聚合函数中使用UDF:即使MYCUSTOMAVG()尚不支持,AVG(MYCUSTOMFUNC())也可以正常工作!

UDF是本地的。为了在群集上使用它们,您必须在其所有节点上放置相同的库,并在所有节点上也运行正确的CREATE FUNCTION语句。在将来的版本中,这可能会更改。

UDF编程介绍#

UDF接口是纯C的。因此,您通常会用C或C ++编写UDF。(即使理论上也可以使用其他语言。)

您的第一个起点应该是src/udfexample.c我们的示例UDF库。该库实现了几种不同的功能,以演示如何使用几种不同的技术(无状态和有状态的UDF,不同的参数类型,批处理调用等)。

提供UDF接口的文件是:

  • src/sphinxudf.h 声明基本类型和辅助函数;
  • src/sphinxudf.c 实现这些功能。

对于实现排序且因此不需要处理FACTORS()参数的UDF ,只需包含sphinxudf.h标头就足够了。

为了能够解析FACTORS()UDF中的Blob,您还需要编译并链接sphinxudf.c源文件。

这两个sphinxudf.hsphinxudf.c是独立的。因此,您只能在这些文件周围进行复制。它们不依赖于Sphinx源代码的任何其他位。

在您的UDF中,您应该仅实现和导出两个函数。

首先,必须定义int LIBRARYNAME_ver() { return SPH_UDF_VERSION; }才能实现UDF接口版本控制。LIBRARYNAME应该替换为您的库的名称。这是一个更完整的示例:

#include <sphinxudf.h>

// our library will be called udfexample.so, thus, so it must define
// a version function named udfexample_ver()
int udfexample_ver()
{
    return SPH_UDF_VERSION;
}

此版本检查器可防止您意外加载UDF接口版本不匹配的库。(这通常会导致错误的行为或崩溃。)

其次,您还必须实现实际功能。例如:

sphinx_int64_t testfunc(SPH_UDF_INIT * init, SPH_UDF_ARGS * args,
    char * error_message)
{
   return 123;
}

SphinxQL中的UDF函数名称不区分大小写。但是,相应的C / C ++ 函数名称必须全部为小写字母,否则UDF将无法加载。

更重要的是,至关重要的是:

  1. 调用约定为C(aka __cdecl);
  2. 参数列表与插件系统期望完全匹配;
  3. 返回类型与您在其中指定的返回类型匹配CREATE FUNCTION
  4. 实现的C / C ++函数是线程安全的。

不幸的是,没有(简便)的方法可以searchd在加载函数时自动检查那些错误,并且它们可能使服务器崩溃和/或导致意外结果。

让我们testfunc()更详细地讨论这个简单的例子。

第一个参数是指向SPH_UDF_INIT结构的指针,本质上仅是指向我们函数状态的指针。使用该状态是可选的。在此示例中,该函数是无状态的,每次调用该函数都会简单地返回123。因此,我们不必定义初始化函数,而可以简单地忽略该参数。

第二个参数是指向的指针SPH_UDF_ARGS,这是最重要的一个。所有实际的调用参数都通过此结构传递到UDF。它包含调用参数计数,名称,类型等。因此,是否像简单常量一样调用函数,如下所示:

SELECT id, testfunc(1) ...

或将一堆子表达式作为其参数,例如:

SELECT id, testfunc('abc', 1000*id+gid, WEIGHT()) ...

或以其他方式,SPH_UDF_ARGS所有这些情况下,它将获得完全相同的结构。但是,在args结构中传递的数据可能有所不同。

testfunc(1)调用示例args->arg_count中将其设置为1,因为自然地我们只有一个参数。在第二个示例中,arg_count它将等于3。另外,args->arg_types对于这两个调用,数组将包含不同的类型数据。等等。

最后,第三个参数char * error_message既用作错误标志,又用作报告人类可读消息(如果有)的方法。UDF应该仅举起该标志/消息以指示不可恢复的内部错误;这样会阻止后续尝试评估该UDF调用实例的任何尝试继续进行。

不得使用此标志进行参数类型检查或“正常”使用过程中可能发生的任何其他错误报告。此标志旨在仅报告突发的严重运行时错误,例如内存不足。

例如,如果我们需要分配临时存储空间供我们的函数使用,或者预先检查参数是否属于受支持的类型,则我们需要再添加两个函数,分别使用UDF初始化和反初始化。

int testfunc_init ( SPH_UDF_INIT * init, SPH_UDF_ARGS * args,
    char * error_message )
{
    // allocate and initialize a little bit of temporary storage
    init->func_data = malloc(sizeof(int));
    *(int*)init->func_data = 123;

    // return a success code
    return 0;
}

void testfunc_deinit(SPH_UDF_INIT * init)
{
    // free up our temporary storage
    free(init->func_data);
}

注意如何testfunc_init()也接收调用参数结构。到那时,我们还没有任何实际的每行,所以args->arg_values将会是NULL。但是参数名称和类型是已知的,并将被传递。您可以在初始化函数中检查它们,如果它们是不受支持的类型,则返回错误。

UDF参数和返回类型#

UDF可以接收几乎任何有效的内部Sphinx类型的参数。如有疑问,请参阅sphinx_udf_argtype枚举以sphinxudf.h获取完整列表。为方便起见,这是一个简短的参考表:

UDF arg类型 C / C ++类型和简短说明
UINT32 uint32_t,无符号32位整数 --
INT64 int64_t,带符号的64位整数 --
浮动 float,单精度(32位)IEEE754 float --
char *,非ASCIIZ字符串,具有单独的长度
UINT32SET uint32_t *,u32整数的排序集
INT64SET int64_t *,一组i64整数排序
因素 void *,带有等级信号的特殊Blob --
JSON格式 char *,JSON(子)对象或字符串格式的字段 --
FLOAT_VEC float *,未排序的浮点数组

Len表中的列表示,args->str_lengths[i]除了参数值args->arg_values[i]本身之外,参数长度还通过单独传递。

对于STRING参数,长度包含字符串长度(以字节为单位)。对于所有其他类型,它包含元素的数量。

至于返回类型,UDF当前可以返回数字或字符串值。各自的类型如下:

狮身人面像类型 定期退货类型 批量输出arg类型
INTEGER sphinx_int64_t int *
BIGINT sphinx_int64_t sphinx_int64_t *
FLOAT double float *
STRING char * --

批处理呼叫将在下面讨论。

为了清晰和方便起见,我们仍然定义了我们自己的sphinx_int64_t类型sphinxudf.h,但是如今,任何标准的64位整数类型(例如int64_tlong long也应该)就足够了,并且可以在您的UDF代码中安全使用。

还要注意,对于STRING类型,您必须使用args->fn_mallocfunction来分配返回的值。

请注意,您可以在UDF 内部使用所需的任何分配器,因此,testfunc_init()即使上面的示例malloc()直接使用,它也是正确的。您可以自己管理该指针,并通过匹配free()调用将其释放,一切都很好。但是,返回的字符串值将由Sphinx管理,并且我们有自己的分配器。因此,对于具体的返回值,您也需要使用它。

UDF呼叫批处理#

从v.3.3开始,Sphinx支持两种类型的带有数字返回类型的“主” UDF调用:

  • 定期,一次恰好叫1行;
  • 批处理,一次调用1到128行。

这两种类型具有不同的C / C ++签名,例如:

/// regular call that RETURNS INTEGER
/// note the `sphinx_int64_t` ret type
sphinx_int64_t foo(SPH_UDF_INIT * init, SPH_UDF_ARGS * args,
    char * error);

/// batched call that RETURNS INTEGER
/// note the `int *` out arg type
void foo_batch(SPH_UDF_INIT * init, SPH_UDF_ARGS * args,
    int * results, int batch_size, char * error);

UDF必须定义这两个函数中的至少一个。从v.3.3开始,UDF可以定义这两个函数,但批处理调用具有优先权。因此,当foo_batch()foo()都定义时,引擎将只使用foo_batch(),而完全忽略foo()

需要批量调用才能提高性能。例如,使用某些CatBoost ML模型一次处理多个文档可能会快5倍以上。

如前所述,由于性能原因,批处理调用的返回类型与常规调用不同。是的,上面示例中的类型是正确的。为简化起见,在使用或创建函数时,常规的单行foo()调用必须使用sphinx_int64_t其返回类型。但是,批处理的多行调用必须使用类型为的输出缓冲区,当使用; 创建时;或使用创建时键入的缓冲区;就像前面的类型表中提到的那样。RETURNS INTEGER``RETURNS BIGINT``foo_batch()``int *``RETURNS INTEGER``sphinx_int64_t *``RETURNS BIGINT

当前的目标批次大小为128,但将来可能会在任一方向上改变。假设知之甚少batch_size,而且非常肯定也没有硬编码的限流的任何地方。(但是,可以合理地假设批次始终在1到65536之间。)

引擎应累积符合目标大小的匹配项,以便大多数UDF调用接收完整的批处理。但是,尾随批次将任意调整大小。例如,对于397个匹配项,应该有4个对的调用foo_batch(),每个批次分别有128、128、128和13个匹配项。

对于批处理中的每个匹配项,参数(及其大小,如果适用)都按顺序存储到arg_values(和str_lengths)中。例如,您可以按以下方式访问它们:

for (int row = 0; row < batch_size; row++)
    for (int arg = 0; arg < args->arg_count; arg++)
    {
        int index = row * args->args_count + arg;
        use_arg(args->arg_values[index], args->str_lengths[index]);
    }

批处理的UDF 必须用一些合理的默认值填充整个结果数组,即使它决定在批处理的中间失败并出现不可恢复的错误。它绝不能返回垃圾结果。

发生错误时,引擎将停止为当前SELECT查询的其余部分调用批处理的UDF (就像处理常规UDF一样),并自动将其余值清零。但是,无论如何,UDF都有责任完全填充失败的批次。

当前仅数字UDF支持批量调用,即。函数返回INTEGERBIGINTFLOAT; STRING函数尚不支持批处理。将来可能会改变。

FACTORS()在UDF中使用#

大多数类型直接映射到相应的C类型。最值得注意的例外是SPH_UDF_TYPE_FACTORS参数类型。您可以通过将FACTORS()表达式作为参数传递给UDF来获得该类型。UDF将接收的值是特殊内部格式的二进制Blob。

要从该Blob中提取单个排名信号,您需要使用两个sphinx_factors_XXX()sphinx_get_YYY_factor()函数族中的一个。

第一个家族仅包含3个功能:

  • sphinx_factors_init()初始化解压缩的SPH_UDF_FACTORS结构;
  • sphinx_factors_unpack() 将二进制blob值解压缩到其中;
  • sphinx_factors_deinit()清理一个deallocate SPH_UDF_FACTORS

所以,你需要调用init()unpack()第一,那么你可以使用领域内的SPH_UDF_FACTORS结构,然后你必须调用deinit()进行清理。结果代码非常简单,如下所示:

// init!
SPH_UDF_FACTORS F;
sphinx_factors_init(&F);

if (sphinx_factors_unpack((const unsigned int *)args->arg_values[0], &F))
{
    sphinx_factors_deinit(&F); // no leaks please
    return -1;
}

// process!
int result = F.field[3].hit_count;
// ... maybe more math here ...

// cleanup!
sphinx_factors_deinit(&F);
return result;

但是,这种访问简单性具有明显的缺点。这将导致每个处理的文档分配多个内存(分别由init()和分别unpack()释放deinit(),然后由释放),这可能会很慢。

因此,存在另一个访问接口,FACTORS()该接口包含许多sphinx_get_YYY_factor()功能。它比较冗长,但是它直接访问Blob数据,并且保证零分配和零复制。因此,要获得排名第一的UDF性能,您需要这样的性能。下面是匹配的示例代码,该示例代码也仅从1个字段访问1个信号:

// init!
const unsigned int * F = (const unsigned int *)args->arg_values[0];
const unsigned int * field3 = sphinx_get_field_factors(F, 3);

// process!
int result = sphinx_get_field_factor_int(field3, SPH_FIELDF_HIT_COUNT);
// ... maybe more math here ...

// done! no cleanup needed
return result;

UDF调用序列#

根据查询中UDF的使用方式,主函数调用(testfunc()在我们正在运行的示例中)可能以完全不同的数量和顺序被调用。特别,

  • 中引用的UDF WHEREORDER BYGROUP BY条款必须和将每一个匹配的文档进行评估。它们将以自然匹配的顺序被调用。
  • 如果没有子选择,则可以在最终LIMIT子句的最后阶段对UDF进行评估,但不应用子句。将按照结果集顺序调用它们。
  • 带有子选择,这样的UDF也会应用inner LIMIT子句之后求值。

但是,其他函数的调用顺序是固定的。即

  • testfunc_init()初始化查询时调用一次。它可以返回一个非零的代码来指示失败。在这种情况下,查询会提早终止,并error_message返回缓冲区中的错误消息。
  • testfunc()testfunc_batch()每当Sphinx需要计算UDF值时,为每个合格的行批处理调用(或参见上文)。该调用可以通过将值写入1或一些人类可读的消息来指示不可恢复的错误error_message。(换句话说,您可以将error_message其用作布尔标志或字符串缓冲区。)
  • error_message从主UDF调用获取非零值后,引擎保证停止为其余查询的后续行调用该UDF调用。默认使用数字的返回值0和字符串的空字符串。Sphinx可能会或可能不会选择尽早终止此类查询,目前尚不能保证任何行为。
  • testfunc_deinit()当查询处理(在给定的索引分片中)结束时,将调用一次。即使主调用先前报告了不可恢复的错误,也必须调用它。

索引:CSV和TSV文件#

indexer支持分别通过csvpipetsvpipe类型将CSV和TSV格式的数据索引。这是有关各自源指令的简短备忘单。

  • csvpipe_command = ...指定要运行的命令(例如,csvpipe_command = cat mydata.csv在最简单的情况下)。
  • csvpipe_header = 1告诉,indexer从第一行中选择列列表(否则,默认情况下,必须在配置文件中指定列列表)。
  • csvpipe_attr_XXX(其中,XXX是一种属性类型,即的一个bigintboolfloatjsonmultimulti_64stringtimestamp,或uint)指定针对给定的列中的属性类型。
  • csvpipe_fieldcsvpipe_field_string指定一个常规的全文字段和一个全文字段,这些字段也应分别存储为string属性。
  • csvpipe_delimiter将列定界符更改为给定字符(csvpipe仅此;tsvpipe自然使用制表符)。

当使用TSV的工作,你可以使用同样的指令,但他们开始tsvpipe前缀(即 tsvpipe_commandtsvpipe_header等)。

第一列目前始终被视为id,并且必须是唯一的文档标识符。

第一行可以视为列的命名列表(当时csvpipe_header = 1),也可以视为实际数据的第一行。默认情况下,将其视为数据。列名被修剪,因此不应该有多余的空格。

csvpipe_header 影响CSV输入列与Sphinx属性和字段的匹配方式。

随着csvpipe_header = 0输入文件只包含数据,所以列的顺序是从配置文件中获取。因此,在这种情况下,csvpipe_attr_XXX和的顺序csvpipe_field非常重要。您将必须明确声明所有字段和属性(开头除外id),并以完全相同的顺序声明它们在CSV文件中出现的顺序。indexer如果存在不匹配或多余的列,则会发出警告。

随着csvpipe_header = 1与列名列表中输入文件开始,所以从配置文件中的声明只用来调整类型。因此,在这种情况下,csvpipe_attr_XXXand csvpipe_field指令的顺序不再重要。另外,默认情况下,所有输入的CSV列都将被视为字段,因此您只需要显式配置属性,而不需要配置字段。例如,以下代码应该可以很好地工作,并自动将index titlecontentas设置为字段:

1.csv:

id, gid, title, content
123, 11, hello world, document number one
124, 12, hello again, document number two

sphinx.conf:

source csv1
{
    type = csvpipe
    csvpipe_command = cat 1.csv
    csvpipe_header = 1
    csvpipe_attr_uint = gid
}

索引:特殊字符,混合令牌和混合代码#

Sphinx提供了一些工具来帮助您更好地建立索引(然后再进行搜索):

  • 其中带有特殊字符的术语,例如@RihannaProcter&GambleU.S.A,等等;
  • 包含字母和数字的术语,例如UE53N5740AU

两种情况下的通用方法(所谓的“混合”)相同:

  • 我们总是存储某些“基础”(最细粒度)的令牌化;
  • 我们还按照配置另外存储(“混合”)额外的令牌;
  • 然后,您可以搜索原始令牌或额外令牌。

因此,在上面的示例中,Sphinx可以:

  • 索引基本标记,例如rihannaue53n5740au
  • 索引特殊标记,例如@rihanna;
  • 混合代码标记的索引部分,例如ue 53ue53

混合令牌(带有特殊字符)#

要索引混合令牌(即其中带有特殊字符的令牌),您应该:

  • blend_chars指令中添加特殊的“混合”字符;
  • 使用blend_mode指令为额外令牌配置几种处理模式(可选);
  • 重建索引。

混合字符将被编入索引既作为分隔符,并在同一时间作为有效字符。在生成基本标记时(或简称为“基本拆分”),它们被视为分隔符。但除此之外,在生成额外令牌时,它们也被视为有效字符。

例如,当您设置blend_chars = @, &, .和索引文本@Rihanna Procter&Gamble U.S.A,基地分裂存储以下六个令牌,最终指数:rihannaproctergambleus,和a。完全不像blend_chars,仅基于charset_table

而且由于blend_chars设置,以下三个额外的令牌获取存储:@rihannaprocter&gamble,和u.s.a。常规字符仍然根据进行大小写折叠charset_table,但是现在保留了那些特殊的混合字符。与被视为空白不同,就像它们在基本拆分中一样。到目前为止,一切都很好。

但是,为什么不只是添加@, &, .charset_table呢?因为那样,我们将完全失去基本分裂。@rihanna会存储三个“魔术”令牌。然后搜索它们的“部分”(例如,对于just rihanna或just gamble)将不起作用。嗯

最后但并非最不重要的一点是,将对场内令牌位置进行相应调整,并在基本令牌和额外令牌之间共享:

  • pos 1,rihanna@rihanna
  • pos 2,procterprocter&gamble
  • 位置3, gamble
  • pos 4,uu.s.a
  • 位置5 s
  • 位置6 a

底线是blend_chars让您丰富索引并在其中存储带有特殊字符的额外令牌。这可能是对基于的常规标记化的方便补充charset_table

混合代码(带字母和数字)#

要索引混合代码,即混合字母和数字的术语,需要启用blend_mixed_codes = 1设置(并重新索引)。

这样,Sphinx 在进行基本拆分时会在字母数字边界上添加额外的空格,但仍将完整的原始令牌存储为额外的。例如,UE53N5740AU分解为5部分:

  • pos 1,ueue53n5740au
  • 位置2 53
  • 位置3, n
  • 位置4 5740
  • 位置5 au

除了“完整”拆分和“原始”代码外,还可以存储前缀和后缀。请参阅blend_mode下面的讨论。

还要注意,在某些输入数据上,混合代码索引可能会生成许多不希望的噪声标记。所以,当你有一个数字,也有特殊条款领域的并不需要被处理为混合代码(考虑任何条款像_category1234,或者只是长的URL),您可以使用mixed_codes_fields指令和限制混合代码索引只可读的文本字段。例如:

blend_mixed_codes = 1
mixed_codes_fields = title, content

这样可以为您节省大量的索引大小和索引时间。

混合模式#

生成额外令牌的方法不止一种。因此,有一个指令来控制它。它被称为blend_mode,它允许您列出所需的所有不同处理变体:

  • trim_none,存储带有所有混合字符的完整令牌;
  • trim_head,存储带有修剪的标题混合字符的令牌;
  • trim_tail,存储带有修剪的尾随混合字符的令牌;
  • trim_both,存储带有修剪的标题和尾随混合字符的令牌;
  • skip_pure,千万不能存储只包含混合字符标记;
  • prefix_tokens,存储所有可能的前缀令牌;
  • suffix_tokens,存储所有可能的后缀标记。

要稍微看到所有这些修剪,请考虑以下设置:

blend_chars = @, !
blend_mode = trim_none, trim_head, trim_tail, trim_both

doc_title = @someone!

在这种情况下,将索引大量额外的令牌:

  • someone 基本分割;
  • @someone!trim_none;
  • someone!trim_head;
  • @someonetrim_tail;
  • someone(是)trim_both

trim_both选项在这里可能看起来有些多余。但是请考虑一个更复杂的术语,例如&U.S.A!将所有特殊字符混合在一起的术语。它的基本分割为三个令牌(us,和a); 它的原始完整格式(存储为trim_none)是小写的&u.s.a!;因此,此术语trim_both是仍然生成清理后的u.s.a变体的唯一方法。

prefix_tokens并且suffix_tokens实际上也开始在相同的&U.S.A!示例中产生不平凡的东西。在记录中,这是因为其基本拆分足够长,最多3个令牌。prefix_tokens将是存储(有用)u.s前缀的唯一方法;并suffix_tokens依次存储(可疑的)s.a后缀。

但是prefix_tokensand suffix_tokens模式对于索引混合代码当然特别有用。blend_mode = prefix_tokens在我们的运行示例中存储了以下内容:

  • 位置1, ,ueue53ue53nue53n5740ue53n5740au
  • 位置2 53
  • 位置3, n
  • 位置4 5740
  • 位置5 au

blend_mode = suffix_tokens分别:

  • pos 1,ueue53n5740au
  • pos 2,5353n5740au
  • 位置3,n以及n5740au
  • pos 4,57405740au
  • 位置5 au

当然,仍然可能缺少组合。例如,ue 53n查询仍然不匹配任何一个。但是,目前我们有意决定避免对所有可能的基本令牌子序列编制索引,因为这似乎会产生过多的噪音。

搜索与混合令牌和混合代码#

经验法则很简单。所有额外的令牌都是仅索引的。在查询中,所有标记均按“原样”对待。

混合字符将在查询中被视为有效字符,并且需要匹配。

例如,查询"@rihanna"匹配Robyn Rihanna Fenty is a Barbadian-born singer文档。但是,查询just rihanna将同时匹配该文档和@rihanna doesn't tweet all that much文档。

混合代码打算在查询自动“切片”。

例如,查询UE53不会自动匹配既不UE 53也不UE 37 53文档。您需要为此在查询词中手动添加额外的空格。

搜索:查询语法#

默认情况下,Sphinx中的全文查询被视为简单的“单词袋”,并且文档中需要所有关键字才能匹配。换句话说,默认情况下,我们对所有关键字执行严格的布尔AND运算。

但是,文本查询不仅具有灵活性,而且Sphinx具有自己的全文查询语言来展现这种灵活性。

你基本上是使用该语言MATCH()在条款SELECT的语句。因此,在本节中,hello world为简洁起见,当我们仅引用(文本)查询时,将要运行的实际完整SphinxQL语句类似于SELECT *, WEIGHT() FROM myindex WHERE MATCH('hello world')

也就是说,让我们从几个关键概念和一个备忘单开始。

经营者#

运算符通常在任意子表达式上工作。例如,您可以根据需要使用运算符AND和OR(和方括号)组合关键字,并以此方式构建任何布尔表达式。

但是,有许多例外。并非所有运营商都普遍兼容。例如,词组运算符(双引号)自然仅对关键字有效。您不能从任意布尔表达式构建“短语”。

一些运算符使用特殊字符,例如短语运算符使用双引号:"this is phrase"。因此,有时您可能不得不从最终用户查询中过滤掉一些特殊字符,以避免无意中触发这些运算符。

其他是文字,它们的语法是全大写的关键字。例如,MAYBE运算符将完全用于(rick MAYBE morty)查询中。为了避免触发这些运算符,将查询小写就足够了:rick maybe morty再次只是一个常规的词袋查询,只需要匹配所有3个关键字即可。

修饰符#

修饰符附加在各个关键字上,它们必须始终有效,并且必须在任何运算符中允许使用。因此,那里没有兼容性问题!

有几个示例是精确形式修饰符或字段开始修饰符=exact ^start。它们将“ their”关键字的匹配分别限制为其确切的形态或在(任何)字段的开头。

备忘单#

从3.2版开始,每个关键字修饰符只有4个。

修饰符 描述
确切形式 =cats 仅匹配此确切形式,需要 index_exact_words
现场开始 ^hello 仅在(任何)字段的开头匹配
现场结束 world$ 仅在(任意)字段的末尾匹配
IDF助力 boost^1.23 排名时将关键字IDF乘以给定值

操作员更有趣!

操作员 描述
括号 (one two) 分组子表达式
one two 匹配两个参数
要么 one | two 匹配任何参数
项或 one || two 匹配任何关键字,并重复使用查询中的位置
one -two 匹配第一个参数,但排除第二个参数的匹配
one !two 匹配第一个参数,但排除第二个参数的匹配
也许 one MAYBE two 匹配第一个参数,但在排名时包含第二个参数
场限制 @title one @body two 限制匹配给定字段
字段限制 @(title,body) test 将匹配限制为给定字段
字段限制 @!(phone,year) test 将匹配限制为所有非给定字段
字段限制 @* test 重置任何先前的字段限制
位置限制 @title[50] test 将匹配限制为字段中的N个第一位置
短语 "one two" 将所有关键字匹配为(完全)短语
短语 "one * * four" 将所有关键字匹配为(完全)短语
接近 "one two"~3 匹配接近窗口内的所有关键字
法定人数 "uno due tre"/2 匹配所有关键字中的任意N个
法定人数 "uno due tre"/0.7 匹配所有关键字的任何给定分数
之前 one << two 仅按此特定顺序匹配args
one NEAR/3 "two three" 在给定距离内以任意顺序匹配args
句子 one SENTENCE "two three" 用一句话匹配args;需要index_sp
one PARAGRAPH two 在一个段落中匹配参数;需要index_sp
ZONE:(h3,h4) one two 仅在给定区域中匹配;需要index_zones
区域跨度 ZONESPAN:(h3,h4) one two 仅在连续范围内匹配;需要index_zones

现在让我们更详细地讨论所有这些修饰符和运算符。

关键字修饰符#

确切的形式修饰符仅在启用了形态(即词干或词法化)后才适用。启用形态后,Sphinx默认会搜索标准化的关键字。此修饰符使您可以搜索确切的原始形式。需要index_exact_words启用设置。

语法=在关键字start处。

=exact

例如,假设启用英语词干,即。索引已通过morphology = stem_en设置进行配置。还假设我们有以下三个示例文档:

id, content
1, run
2, runs
3, running

如果不使用index_exact_words,则仅将规范化的形式(即run)存储到每个文档的索引中。即使使用修饰符,也无法区分它们。

使用index_exact_words = 1,标准化和原始关键字形式都存储在索引中。但是,默认情况下,搜索时也会对关键字进行标准化。因此,查询runs将被标准化为run,并且仍将匹配所有3个文档。

最后,通过index_exact_words = 1和带有精确的表单修饰符,类似的查询=runs将能够仅匹配原始表单,并仅返回文档#2。

为了方便起见,您还可以将此特定修饰符应用于整个短语运算符,它将向下传播到所有关键字。

="runs down the hills"
"=runs =down =the =hills"

当且仅当关键字出现在(任何)全文字段的开始时,字段开始修饰符才使关键字匹配。(从技术上讲,它将仅匹配字段位置为1的发布。)

语法^在关键字start处,在正则表达式后进行模仿。

^fieldstart

当且仅当关键字出现在(任何)全文字段的末尾时,字段结束修饰符才使关键字匹配。(从技术上讲,它将仅匹配带有特殊内部“字段结束”标志的帖子。)

语法$在关键字start处,在正则表达式后进行模仿。

fieldend$

使用IDF提升修饰符,您可以调整关键字IDF值(用于排名),它将IDF值乘以给定常数。这影响了基于IDF的许多排名因素。反过来,这也会影响默认排名。

语法^后跟一个比例常数。

boostme^1.23

布尔运算符(括号,AND,OR,NOT)#

这些使您可以实现分组(带有方括号)和经典的布尔逻辑。相应的正式语法如下:

  • 括号: (expr1)
  • 和: expr1 expr2
  • 要么: expr1 | expr2
  • 不:-expr1!expr1

其中expr1expr2是关键字或任何其他可计算文本查询表达式。这里有一些查询示例,显示了所有运算符。

(shaken !stirred)
"barack obama" (alaska | california | texas | "new york")
one -(two | (three -four))

没有什么太激动了,看不到这里。但是仍然有一些值得一提的怪癖。他们在这里,没有特别的顺序。

OR运算符的优先级高于AND。

换句话说,OR优先,首先评估它们,然后在OR之上评估AND。因此,looking for cat | dog | mouse查询等效于looking for (cat | dog | mouse)而不 等效于(looking for cat) | dog | mouse

AND是隐式的。

Sphinx中没有针对它们的任何显式语法。只需将两个表达式彼此相邻就可以了。

没有AND / OR / NOT的全大写版本,这些都是有效的关键字。

因此,类似的东西rick AND morty就等于rick and morty,这两个查询都需要匹配所有3个关键字,包括文字and

请注意,此行为与(例如)之间的行为有所不同rick MAYBE morty,其中运算符MAYBE的语法为all-caps关键字。

字段和区域限制会影响整个(子)表达式。

这意味着查询中的@title限制@title hello world适用于所有关键字,而不仅仅是限制运算符之后的关键字或表达式。此示例中的两个关键字都需要在title字段中匹配,而不仅是第一个hello。编写此查询的显式方式为,每个关键字都有明确的字段限制(@title hello) (@title world)

括号内推和弹出字段和区域限制。

例如,(@title hello) world查询仅需hello匹配title。但是该限制以右括号结束,然后world可以再次匹配文档中的任何位置。因此,查询等效于(@title hello) (@* world)

更奇怪的是,但可以预见,@body (@title hello) world查询反过来等效于(@title hello) (@body world)。第一个@body限制在开括号中被推入,然后在结束的括号中被恢复。

相同规则适用于区域,请参阅下面的ZONEZONESPAN运算符。

布尔运算符中的查询中位置是顺序的。

尽管它们不影响匹配(也称为基于文本的过滤),但它们确实会影响排名。例如,即使您将一个短语与OR拼接在一起,一个相当重要的“短语匹配度”排名因子(称为“ lcs”)也不会改变,即使匹配变化很大:

mysql> select id, weight(), title from test1
  where match('@title little black dress');
+--------+----------+--------------------+
| id     | weight() | title              |
+--------+----------+--------------------+
| 334757 |     3582 | Little black dress |
+--------+----------+--------------------+
1 row in set (0.01 sec)

mysql> select id, weight(), title from test1
  where match('@title little | black | dress');
+--------+----------+------------------------+
| id     | weight() | title                  |
+--------+----------+------------------------+
| 334757 |     3582 | Little black dress     |
| 420209 |     2549 | Little Black Backpack. |
...

因此,从某种意义上讲,您使用方括号和运算符构造的所有内容仍然看起来像是排名代码的一个巨大“短语”(实际上是一堆词)。好像没有括号,也没有运算符。

运算符NOT真的是运算符ANDNOT。

尽管-something可以从技术上计算查询,但这种查询更多时候只是编程错误。这可能是一个昂贵的选择,因为索引中所有文档的隐式列表可能会很大。这里有一些例子。

// correct query, computable at every level
aaa -(bbb -(ccc ddd))

// non-computable queries
-aaa
aaa | -bbb

(附带说明,这也可能引发对包含零匹配关键字的文档进行排名的哲学问题;值得庆幸的是,从工程学的角度来看,仅将权重设置为零也很容易残酷地削减Gordian结。)

因此,NOT运算符需要左侧可计算的内容。孤立的NOT会引发查询错误。如果您绝对必须这样做,则可以__allmydocs在建立索引时在所有文档中附加一些特殊的magic关键字(如,类似您的喜好)。上面的两个示例不可计算查询将变为:

(__allmydocs -aaa)
aaa | (__allmydocs -bbb)

操作员不仅在学期开始时工作。

为了触发,必须在前面加上空格,方括号或其他清晰的关键字边界。例如,cat-dog默认情况下实际上是等效于cat dog,而cat -dog带有空格的确将运算符NOT应用于dog

词组运算符#

词组运算符使用实际的标准双引号语法,基本上可以让您搜索确切的词组,即。几个关键字以完全相同的顺序,它们之间没有任何空格。例如。

"mary had a little lamb"

是的,无聊。但是,当然,对于这个简单的运算符,甚至还有更多。

精确的形式修饰符适用于整个运算符。当然,任何修饰语都必须在短语中起作用,这就是修饰语的全部含义。但是使用精确的形式修饰符,可以使用额外的语法糖,将其立即应用于整个词组:="runs down the hills"形式比编写起来要容易一些"=runs =down =the =hills"

独立星号“匹配”任何关键字。或者更确切地说,他们在匹配词组时会跳过该位置。文本查询实际上不适用于文档文本。他们仅使用指定的关键字,并分析其在文档中和在查询中的位置。现在,短语运算符中的特殊星号实际上不会匹配任何内容,它只会在解析查询时调整查询位置。因此,对搜索性能完全没有影响,但是词组关键字位置将发生变化。例如。

"mary had * * lamb"

停用词可“匹配”任何关键字。同样的逻辑适用于停用词。停用词甚至都没有存储在索引中,因此我们没有任何匹配项。但是,即使在停用词上,我们仍需要同时调整索引编制时的文档内位置和匹配时的查询内位置。

有时这会导致一些违反直觉和意外(但不可避免)的匹配行为。考虑以下文件集:

id, content
1, Microsoft Office 2016
2, we are using a lot of software from Microsoft in the office
3, Microsoft opens another office in the UK

假设inthe是我们唯一的禁用词。以下两个词组查询将匹配哪些文档?

  1. "microsoft office"
  2. "microsoft in the office"

查询#1仅与文档#1匹配,这并不奇怪。但是,正如我们刚刚讨论的那样"microsoft * * office",由于停用词,查询#2实际上等效于。因此,它与文档#2和#3都匹配。

MAYBE运算子#

排名有时可能需要运算符MAYBE。它使用两个任意表达式,并且仅需要第一个表达式匹配,但是使用第二个表达式的(可选)匹配项进行排名。

expr1 MAYBE expr2

例如,rick MAYBE morty查询完全相同的文件刚刚匹配rick,还加上MAYBE,提及这两份文件rick,并morty会得到较高的排名。

支持任意表达式,因此这也是有效的:

rick MAYBE morty MAYBE (season (one || two || three) -four')

术语“或”运算符#

术语“或”运算符(双管道)实际上使您可以在查询时指定每个关键字同义词的“适当排名”。

匹配的角度来看,它只是做普通布尔或通过几个关键字,但排名明智的(而不像定期或运营商),它并不会增加他们的查询位置。这样可以保持所有位置排名因素不变。

自然,它仅接受单个关键字,您不能对关键字和短语或任何其他表达式进行术语或运算。同样,短语或接近运算符目前不支持term-OR,尽管这是一个有趣的可能性。

用一个简单的例子来说明它是最容易的。假设我们仍在搜索那条黑色小礼服,就像我们在常规OR运算符示例中所做的那样。

mysql> select id, weight(), title from rt
  where match('little black dress');
+------+----------+-----------------------------------------------+
| id   | weight() | title                                         |
+------+----------+-----------------------------------------------+
|    1 |     3566 | little black dress                            |
|    3 |     1566 | huge black/charcoal dress with a little white |
+------+----------+-----------------------------------------------+
2 rows in set (0.00 sec)

到目前为止,一切都很好。但看起来charcoal是我们可以在此处使用的同义词。让我们尝试使用常规的OR运算符使用它。

mysql> select id, weight(), title from rt
  where match('little black|charcoal dress');
+------+----------+-----------------------------------------------+
| id   | weight() | title                                         |
+------+----------+-----------------------------------------------+
|    3 |     3632 | huge black/charcoal dress with a little white |
|    1 |     2566 | little black dress                            |
|    2 |     2566 | little charcoal dress                         |
+------+----------+-----------------------------------------------+
3 rows in set (0.00 sec)

糟糕,发生了什么事?现在,我们还匹配了文档2,这很好,但是为什么文档3突然间排名如此之高?

这是因为使用常规的OR排名基本上会寻找整个查询,就像没有任何运算符一样。理想的词组匹配不仅是"little black dress",还包括"little black charcoal dress"删除了所有特殊运算符的整个查询。

在我们的小型测试数据库中,没有这样的“完美” 4个关键字完整短语匹配。(如果有的话,它将获得最高排名。)从短语排名的角度来看,下一个最好的"black/charcoal dress"部分是与查询匹配的3个关键字子短语。这就是为什么它的排名要更高的原因"little black dress",其中文档和查询之间的最长公共子短语是"little black",只有2个关键字,而不是3个。

但这根本不是我们想要的。我们只是想引入的同义词black,而不是打破排名!这正是“或”运算符的含义。

mysql> select id, weight(), title from rt
  where match('little black||charcoal dress');
+------+----------+-----------------------------------------------+
| id   | weight() | title                                         |
+------+----------+-----------------------------------------------+
|    1 |     3566 | little black dress                            |
|    2 |     3566 | little charcoal dress                         |
|    3 |     2632 | huge black/charcoal dress with a little white |
+------+----------+-----------------------------------------------+
3 rows in set (0.00 sec)

好,排名回到了预期。现在,由于精确的词组匹配(默认排名者青睐),原始的完全匹配"little black dress"和同义词"little charcoal dress"都再次位于顶部。

请注意,尽管以上所有示例都围绕一个位置因子lcs(在默认排名中使用),但还有更多的位置因子。有关更多详细信息,请参见“ 排名因子 ”部分。

位置和位置限制运算符#

字段限制运算符将后续表达式的匹配限制到给定字段或一组字段。字段名称必须存在于索引中,否则查询将因错误而失败。

有几种语法形式可用。

  1. @field将匹配限制为单个给定字段。这是最简单的形式。@(field)也有效。
  2. @(f1,f2,f3)限制匹配多个给定字段。请注意,匹配可能只在其中一个字段中发生。例如,@(title,body) hello world要求这两个关键字非常相同的字段相匹配!像{"id":123, "title":"hello", "body":"world"}(原谅我的JSON)这样的文档确实与此查询匹配。
  3. @!(f1,f2,f3)限制匹配给定字段以外的所有字段。这对于避免将最终用户查询与某些内部系统字段进行匹配非常有用。@!f1如果您只想跳过一个字段,也是有效的语法。
  4. @* 语法会重置所有先前的限制,并重新启用与所有字段匹配的内容。

此外,除以外的所有形式都@*可以跟一个可选[N]子句,该子句N将字段的匹配范围限制为第一个标记(关键字)。以下所有示例均有效:

  • @title[50] test
  • @(title,body)[50] test
  • @!title[50] test

重申一下,磁场极限由方括号“包含”,或更正式地讲,任何电流极限都存储在一个开方括号中,并在一个结束方括号中恢复。

如有疑问,请使用SHOW PLAN找出实际使用的限制:

mysql> set profiling=1;
  select * from rt where match('(@title[50] hello) world') limit 0;
  show plan \G
...

*************************** 1. row ***************************
Variable: transformed_tree
   Value: AND(
  AND(fields=(title), max_field_pos=50, KEYWORD(hello, querypos=1)),
  AND(KEYWORD(world, querypos=2)))
1 row in set (0.00 sec)

我们可以看到,该@title限制仅适用于hello,并且按预期重置为匹配右括号中的所有字段(和位置)。

邻近和NEAR运算符#

邻近运算符可以按任何顺序匹配所有指定的关键字,并在这些关键字之间留出许多空白。形式语法如下:

"keyword1 keyword2 ... keywordM"~N

哪里N有一点怪异的含义。这是那些M指定的关键字之间可能出现的间隔(其他关键字)数量,但额外增加了1。

例如,考虑一个文档,该文档读取"Mary had a little lamb whose fleece was white as snow",并考虑两个查询:"lamb fleece mary"~4"lamb fleece mary"~5。我们之间恰好有4个多余的话marylambfleece,即那些4 hadalittle,和whose。这意味着第一个查询N = 4匹配,因为使用N = 4接近运算符实际上只允许3个间隔,而不是4个间隔。因此,第二个示例查询将匹配,因为N = 5它允许4个间隔(加上1个排列)。

NEAR运算符是接近运算符的广义版本。其语法为:

expr1 NEAR/N expr2

其中N的含义与接近运算符中的含义相同,即允许的间隙数加一。但是使用NEAR,我们可以使用任意表达式,而不仅仅是单个关键字。

(binary | "red black") NEAR/2 tree

左右表达式仍然可以按任何顺序匹配。例如,查询progress NEAR/2 bar将匹配以下两个文档:

  1. progress bar
  2. a bar called Progress

NEAR保持关联,表示arg1 NEAR/X arg2 NEAR/Y arg3将被评估为(arg1 NEAR/X arg2) NEAR/Y arg3。它具有与BEFORE相同(最低)的优先级。

请注意,虽然只有2个关键字,但近距离运算符和NEAR运算符是相同的(例如,"one two"~N并且one NEAR/N two应表现完全相同),而更多的关键字则并非如此。

因为当您使用NEAR堆叠多个关键字时,堆叠中的每个关键字最多N - 1允许空格。考虑有两个堆叠NEAR运营商这个例子:。它在和之间最多允许2个间隔,然后在三个之间最多允许2个间隔。这比具有相同N()的接近运算符的限制要少,因为接近运算符仅允许2个间隙。因此,带有文本的文档将匹配NEAR查询,但匹配邻近查询。one NEAR/3 two NEAR/3 three``one``two``two``"one two three"~3``one aaa two bbb ccc three

反之亦然,如果我们将限制提高到接近所有NEAR允许的总限制,该怎么办?我们得到"one two three"~5(允许有4个间隙,再加上魔术1),因此与NEARs变体匹配的任何内容也将与接近变体匹配。但是,现在的文件one two aaa bbb ccc ddd three不再匹配的临近,因为之间的差距twothree太大。现在,接近运算符的限制变得不再那么严格了。

最重要的是,接近运算符和一组NEAR 并不是真正可互换的,它们匹配的东西有所不同。

法定人数#

仲裁匹配运算符实际上使您可以执行模糊匹配。它不比匹配所有参数关键字严格。它将匹配所有指定的M个关键字中至少包含N个关键字的所有文档。就像接近(或与)一样,那些N可以任何顺序出现。

"keyword1 keyword2 ... keywordM"/N
"keyword1 keyword2 ... keywordM"/fraction

对于特定示例,"the world is a wonderful place"/3将匹配具有指定单词中的任意3个或更多的所有文档。

自然,N必须小于或等于M。而且,M必须为1到256个关键字(包括1和256)之间的任意值。(尽管仅使用1个关键字的仲裁没有多大意义,但这是允许的。)

分数必须介于0.0到1.0之间,更多详细信息请参见下文。

Quorum with N = 1实际上等效于OR的堆栈,可以用作替代它的语法糖。例如,这两个查询是等效的:

red | orange | yellow | green | blue | indigo | violet
"red orange yellow green blue indigo violet"/1

除了绝对数字N,您还可以指定分数,即0.0到1.0之间的浮点数。在这种情况下,Sphinx将N根据运算符中的关键字数量自动进行计算。当您事先不知道或不知道关键字数时,这很有用。上面的示例可以重写为"the world is a wonderful place"/0.5,这意味着我们要匹配至少50%的关键字。由于此查询中有6个单词,因此自动计算的匹配阈值也将为3。

小数阈值四舍五入。因此,使用3个关键字和一个0.5的分数,我们将获得2个关键字的最终阈值,3 * 0.5 = 1.5向上舍入为2。还有一个较低的安全限制1个关键字,因为匹配零个关键字具有零意义。

当法定阈值限制太严格时(即,当N大于M时),该运算符会自动替换为AND运算符。当关键字超过256个时,也会发生相同的后备情况。

严格的订单运算符(之前)#

该运算符对其参数执行严格的“从左到右”顺序(即查询顺序)。参数可以是任意表达式。语法为<<,并且没有大写版本。

expr1 << expr2

例如,black << cat查询将匹配black and white cat的文件,但没有一个that cat was black文件。

严格顺序运算符的优先级最低,与NEAR运算符相同。

它既可以应用于关键字也可以应用于更复杂的表达式,因此以下是有效的查询:

(bag of words) << "exact phrase" << red|green|blue

SENTENCE和PARAGRAPH运算符#

当它们的两个自变量分别位于同一句子或同一文本段落中时,这些运算符将与文档匹配。参数可以是关键字或短语,也可以是同一运算符的实例。(也就是说,您可以堆叠多个SENTENCE运算符或PARAGRAPH运算符。但是不支持将它们混合使用。)以下是一些示例:

one SENTENCE two
one SENTENCE "two three"
one SENTENCE "two three" SENTENCE four

句子或段落中参数的顺序无关紧要。

这些运算符要求在index_sp启用设置(句子和段落索引功能)的情况下建立索引,否则返回纯AND。您可以参考的文档,index_sp以获取有关句子或段落的更多详细信息。

ZONE和ZONESPAN运算符#

区域限制算子有点类似于字段限制算子,但是将匹配限制为给定的现场区域(或区域列表)。支持以下语法变体:

ZONE:h1 test
ZONE:(h2,h3) test
ZONESPAN:h1 test
ZONESPAN:(h2,h3) test

区域被称为字段内的区域。本质上,它们映射到HTML(或XML)标记。<h1>和之间的所有内容都</h1>在一个称为的区域中,h1并且可以与该ZONE:h1 test查询匹配。

请注意,ZONE和ZONESPAN限制不仅会在右括号或下一个区域限制算子上重置,还会在下一个字段限制算子上重置!因此,请确保为每个字段明确指定区域。此外,这使得运营商@*一个完整的复位,即。它应该重置字段和区域限制。

区域限制需要使用区域支持构建的索引(有关index_zones更多详细信息,请参阅文档)。

ZONE和ZONESPAN限制之间的区别在于,前者允许其参数在同一区域的多个不连续跨度中匹配,而后者则要求所有匹配都在单个连续范围内发生。

例如,(ZONE:th hello world)查询与此示例文档匹配。

<th>Table 1. Local awareness of Hello Kitty brand.</th>
.. some table data goes here ..
<th>Table 2. World-wide brand awareness.</th>

在此示例中,我们有2个跨度的th区域,hello将在第一个区域和world第二个区域中匹配。因此,从某种意义上说,ZONE可以处理所有区域跨度的串联。

而且,如果您需要进一步限制与任何单个连续范围的匹配,则应使用ZONESPAN运算符。(ZONESPAN:th hello world)查询与上面的文档匹配。(ZONESPAN:th hello kitty)但是呢!

搜索:地理搜索#

Sphinx可以进行有效的地理搜索,并且相关功能包括:

  • GEODIST() 计算两个地理位置之间距离的函数
  • CONTAINS() 检查地理点是否在地理多边形内的函数
  • 用于快速,早期距离检查的属性索引

地理搜索的属性索引。

当您在纬度和经度列上创建索引(并且应该)时,查询优化器可以在一些重要用GEODIST()例中利用这些索引:

  1. 单常数锚套:
SELECT GEODIST(lat,lon,$lat,$lon) dist ...
    WHERE dist <= $radius
  1. 多个常量锚点情况:
SELECT
    GEODIST(lat,lon,$lat1,$lon1) dist1,
    GEODIST(lat,lon,$lat2,$lon2) dist2,
    GEODIST(lat,lon,$lat3,$lon3) dist3,
    ...,
    (dist1 < $radius1 OR dist2 < $radius2 OR dist3 < $radius3 ...) ok
WHERE ok=1

这些情况对于查询优化器是已知的,并且一旦检测到它们,它就可以选择首先执行一个或多个近似属性索引的读取,而不是扫描整个索引。当快速近似读取具有足够的选择性时(通常发生在搜寻距离足够小的情况下),可以节省大量资金。

案例1处理您的典型“给我一切都足够接近某个点”的搜索。当锚点和半径都恒定时,Sphinx将自动预先计算一个边界框,该边界框完全覆盖一个“圆”,并具有围绕该锚点的所需半径。分别找到两个内部最小/最大纬度和经度值。然后它将快速检查属性索引统计信息,并且如果边界框条件足够有选择性,它将切换到属性索引读取而不是完整扫描。

这是一个有效的查询示例:

SELECT *, GEODIST(lat,lon,55.7540,37.6206,{in=deg,out=km}) AS dist
  FROM myindex WHERE dist<=100

案例2处理多锚搜索,即。“给我足够靠近点1或点2等的文档”。基本方法完全相同,但是会生成多个边界框,执行多个索引读取,并将它们的结果全部合并在一起。

这是另一个例子:

SELECT id,
   GEODIST(lat, lon, 55.777, 37.585, {in=deg,out=km}) d1,
   GEODIST(lat, lon, 55.569, 37.576, {in=deg,out=km}) d2,
   geodist(lat, lon, 56.860, 35.912, {in=deg,out=km}) d3,
   (d1<1 OR d2<1 OR d3<1) ok
FROM myindex WHERE ok=1

请注意,如果我们稍微重新格式化查询,并且优化器不再识别出符合条件的案例,那么优化将不会触发。例如:

SELECT *, 2*GEODIST(lat,lon,55.7540,37.6206,{in=deg,out=km})<=100 AS flag
  FROM myindex WHERE flag=1

显然,在这种情况下,“边界框优化”实际上仍然是可行的,但是优化器不会识别出这种情况,而是切换到全扫描。

为了确保这些优化是否对您有用EXPLAIN,请在查询中使用。另外,进行这些检查时,请确保半径足够小。

另一个有趣的地方是,有时优化器可以相当恰当地选择仅使用一个索引而不是两个索引,或者完全避免使用索引。

说,如果我们的半径覆盖整个国家怎么办?无论如何,我们所有的文档都将在边界框中,并且简单的完全扫描确实会更快。这就是为什么您应该使用带有“足够小”的测试半径的原因EXPLAIN

或说,如果AND id=1234查询中存在另一个超选择条件,该怎么办?进行索引读取也将是多余的,优化器将改为选择执行查找id

搜索:矢量搜索#

您可以使用Sphinx来实现矢量搜索,并且为此提供了几种不同的功能,即:

  • 固定数组属性,例如 rt_attr_int8_array = vec1[128]
  • JSON数组属性,例如。 {"vec2": int8[1,2,3,4]}
  • DOT()计算点积的功能
  • FVEC()指定向量常数的函数

让我们看看所有这些部分如何连接在一起。

首先,存储。您可以使用以下任一选项存储每个文档的向量:

  • 固定大小的固定类型数组,即 XXX_attr_YYY_array指示
  • 具有隐式类型的JSON数组,即 [1,2,3,4]JSON中的常规值
  • 具有显式类型的JSON数组,即 int8[1,2,3,4]float[1,2,3,4]语法扩展

固定阵列访问速度最快,但根本不够灵活。此外,每个文档都需要一些RAM。例如,具有32个浮点数(rt_attr_float_array = test1[32])的固定数组行将消耗128个字节,无论它是否包含任何实际数据(并且没有任何显式数据的数组将填充零)。

JSON数组的访问速度较慢,每行消耗更多的内存,但是该内存仅在每行使用的内存中消耗。这意味着当向量稀疏定义(例如,整个10M集合中只有1M个文档)时,无论如何都应使用JSON来节省一些RAM。

JSON数组在默认情况下也是“混合”的,也就是说,可以包含任意不同类型的值。但是,对于向量搜索,您通常希望使用优化的数组,并在所有值上附加单个类型。Sphinx可以使用int32或int64范围内的值自动检测JSON中的整数数组,并进行有效存储和稍后处理。但是,要在JSON数组上强制使用int8或float类型,必须显式使用我们的JSON语法扩展。

float在JSON中存储值数组,您必须:

  • 可以float使用1.234f语法在每个值中指定类型(因为默认情况下1.234double在JSON中获得类型),例如:[1.0f, 2.0f, 3.0f]
  • 或使用float[...]语法指定数组类型,例如:float[1,2,3]

int8在JSON中存储值的数组(即-128至127,包括端值),唯一的选择是:

  • int8[...]语法指定数组类型,例如:int8[1,2,3]

在这两种情况下,我们都需要一个显式类型来区分两个可能的选项(floatvs doubleint8vs intcase),并且默认情况下,我们选择使用更高的精度而不是节省空间。

第二,计算。这里的主要DOT()功能是计算两个矢量参数之间的点积的函数。相应向量分量的乘积之和。

当然,最常见的用例是DOT()在每个文档数组(作为属性或JSON存储)和常量之间计算a 。后者应指定为FVEC()

SELECT id, DOT(vec1, FVEC(1,2,3,4)) FROM mydocuments
SELECT id, DOT(json.vec2, FVEC(1,2,3,4)) FROM mydocuments

请注意,DOT()根据实际参数类型(即浮点向量或整数向量等)在内部优化其执行。这就是为什么以下两个查询的执行情况大不相同的原因:

mysql> SELECT id, DOT(vec1, FVEC(1,2,3,4,...)) d
  FROM mydocuments ORDER BY d DESC LIMIT 3;
...
3 rows in set (0.047 sec)

mysql> SELECT id, DOT(vec1, FVEC(1.0,2,3,4,...)) d
  FROM mydocuments ORDER BY d DESC LIMIT 3;
...
3 rows in set (0.073 sec)

在此示例中,vec1是一个整数数组,我们将DOT()其与整数常数矢量或浮点常数矢量相对。显然,int乘int和float乘int有点不同,因此性能也有所不同。

排名:因素#

Sphinx使您可以指定用于weight()计算的自定义排名公式,并根据需要定制基于文本的相关性排名。例如:

SELECT *, WEIGHT() FROM myindex WHERE MATCH('hello world')
OPTION ranker=expr('sum(lcs)*1000+bm15')

这种机制称为表达式排名,其排名公式(表达式)可以访问比正则表达式更多的特殊变量,称为排名因子。(当然,这些公式仍然可以访问所有每个文档的属性以及所有数学和其他函数。)

排名因子(也就是排名信号)基本上是根据当前搜索查询为每个文档(甚至字段)计算的一堆不同值。它们本质上描述了特定文档匹配的各个方面,因此它们在排名公式或ML模型中用作输入变量。

有三种类型(或级别)的因素,这些因素确定何时可以准确计算给定的因素:

  • 查询因素:仅取决于搜索查询而不取决于文档的值,例如query_word_count;
  • 文档因子:既取决于查询取决于匹配文档的值,例如doc_word_countbm15
  • 字段系数:既取决于查询取决于匹配的全文本字段的值,例如word_countlcs

查询因子自然会在查询开始时只计算一次,然后从那里开始保持不变。这些通常很简单,例如查询中的许多唯一关键字。您可以在排名公式中的任何位置使用它们。

文档因子还取决于文档文本,因此将为每个匹配的文档进行计算。您也可以在排名公式中的任何位置使用它们。其中,经典bm25()功能的一些变体可以说是对相关性排名最重要的。

最终,场因子更加细化,它们针对每个场进行计算。因此,然后必须通过某种因子聚集函数将它们聚集为一个奇异值(从v.3.2开始,支持的函数为SUM()TOP())。

在更详细地讨论每个特定因素之前,这里有必填因素速查表

  • Sphinx中的点击数 == IR中的帖子==“当前字段中出现了(某些类型的)匹配关键字”
名称 水平 类型 摘要
has_digit_words 询问 整型 has_digit包含[0-9]字符的单词数(但也可能包含其他字符)
is_latin_words 询问 整型 的数目is_latin的话,即 [a-zA-Z]仅带字符的单词
is_noun_words 询问 整型 的数目is_noun的话,即 标记为名词(由lemmatizer标记)
is_number_words 询问 整型 的数目is_number的话,即 [0-9]仅带字符的整数
max_lcs 询问 整型 当前查询的最大LCS值
query_word_count 询问 整型 查询中唯一包含关键字的数量
bm15 doc 整型 快速估算BM25(1.2, 0)无查询语法支持
bm25a(k1,b) doc 整型 BM25()具有可配置K1B常量和语法支持的精确值
bm25f(k1,b,…) doc 整型 BM25F()具有额外的可配置字段权重的精确值
doc_word_count doc 整型 文档中匹配的唯一关键字的数量
field_mask doc 整型 匹配字段的位掩码
atc 领域 浮动 总计术语紧密度,log(1+sum(idf1*idf2*pow(distance, -1.75))超过“最佳”术语对
precision_field_hit 领域 布尔 查询字段是否按查询词顺序完全覆盖
精确命中 领域 布尔 是否查询==字段
确切顺序 领域 布尔 是否所有查询关键字都匹配a)和b)查询顺序
full_field_hit 领域 布尔 查询是否以任意术语顺序完全覆盖了字段
has_digit_hits 领域 整型 has_digit关键词点击
hit_count 领域 整型 任何关键字的点击总数
is_latin_hits 领域 整型 is_latin关键词点击
is_noun_hits 领域 整型 is_noun关键词点击
is_number_hits 领域 整型 is_number关键词点击
生命周期 领域 整型 查询和文档之间的最长公共连续子序列(以字为单位)
生命科学 领域 整型 查询和文档之间的最长公共子序列(以字为单位)
max_idf 领域 浮动 max(idf) 在此字段中匹配的关键字
max_window_hits(n) 领域 整型 max(window_hit_count) 计算当前字段中所有N字窗口
min_best_span_pos 领域 整型 第一个最大LCS跨度位置,换句话说,从1开始
min_gaps 领域 整型 匹配范围内匹配关键字之间的最小间隔数
min_hit_pos 领域 整型 第一个匹配出现位置,换句话说,从1开始
min_idf 领域 浮动 min(idf) 在此字段中匹配的关键字
sum_idf 领域 浮动 sum(idf) 在此字段中匹配的关键字
sum_idf_boost 领域 浮动 sum(idf_boost) 在此字段中匹配的关键字
tf_idf 领域 浮动 sum(tf*idf)匹配关键字过多,即 sum(idf)在所有情况下
user_weight 领域 整型 用户指定的字段权重(通过OPTION field_weights
图书馆 领域 浮动 加权LCCS,sum(idf)连续关键字范围
字数 领域 整型 此字段中匹配的唯一关键字的数量

因子聚集函数#

形式上,(字段)因子聚合函数是一个单参数函数,该函数采用具有字段级因子的表达式,在所有匹配的字段上对其进行迭代,并针对各个每个字段值计算最终结果。

当前支持的聚合功能包括:

  • SUM(),对所有匹配字段的参数表达式求和。例如,sum(1)应返回多个匹配的字段。
  • TOP(),返回所有匹配字段中参数的最大值。例如,top(max_idf)应该在整个文档中返回最大的每个关键字IDF。

自然,仅在具有字段级因素的表达式中才需要这些,查询级和文档级因素可以在公式中“按原样”使用。

关键字标志#

当搜索和排序时,Sphinx会根据感兴趣的几类对每个查询关键字进行分类。也就是说,当关键字是(已知)名词时,它用“名词”类标记关键字,或者当它是整数时,用“数字”类标记关键字。

目前,我们确定了4个关键字类别并分配了相应的标志。这4个标记依次生成8个排名因子,4个查询级每个标记关键字计数和4个字段级每个类别命中计数。这些标志将在下面进行更详细的描述。

重要的是要了解,所有标志实际上都是在查询解析时分配的,而无需查看任何实际的索引数据(与标记化和形态设置相对)。同样,查询处理规则也适用。意味着在分配标志之前,有效的关键字修饰符会被有效剥离。

has_digit#

关键字被标记为has_digit至少有一个数字字符,即。从[0-9]范围内,在该关键字。

允许使用其他字符,这意味着它l33t是一个has_digit关键字。

但是它们不是必需的,因此,is_number根据定义,任何关键字都是has_digit关键字。

is_latin#

is_latin当关键字完全由拉丁字母组成时,即标记为。任何[a-zA-Z]字符。不允许使用其他字符。

例如,hello被标记为is_latin,但是l33t不是因为数字的。

还要注意的是通配符喜欢abc*没有标记为is_latin,即使所有的实际扩展是拉美唯一的。从技术上讲,查询关键字标记仅查看查询本身,而不查看索引数据,并且还不了解任何有关实际扩展的信息。(即使这样做,插入具有新扩展名的新行也可能会突然破坏该is_latin属性。)

同时,当查询关键字修饰符喜欢^abc=abc仍得到正确处理时,这些关键字被标记为正确is_latin

is_noun#

is_noun当(a)至少为索引启用了lemmatizer,并且(b)lemmatizer将独立关键字分类为名词时,将关键字标记为。

例如,通过morphology = lemmatize_en在示例索引中进行配置,我们得到以下信息:

mysql> CALL KEYWORDS('deadly mortal sin', 'en', 1 AS stats);
+------+-----------+------------+------+------+-----------+------------+----------------+----------+---------+-----------+-----------+
| qpos | tokenized | normalized | docs | hits | plain_idf | global_idf | has_global_idf | is_latin | is_noun | is_number | has_digit |
+------+-----------+------------+------+------+-----------+------------+----------------+----------+---------+-----------+-----------+
| 1    | deadly    | deadly     | 0    | 0    | 0.000000  | 0.000000   | 0              | 1        | 0       | 0         | 0         |
| 2    | mortal    | mortal     | 0    | 0    | 0.000000  | 0.000000   | 0              | 1        | 1       | 0         | 0         |
| 3    | sin       | sin        | 0    | 0    | 0.000000  | 0.000000   | 0              | 1        | 1       | 0         | 0         |
+------+-----------+------------+------+------+-----------+------------+----------------+----------+---------+-----------+-----------+
3 rows in set (0.00 sec)

但是,从这个示例中可以看到,is_nounPOS标记并不完全精确。

目前,它只针对单个单词而不是上下文。因此,即使在这种特定的查询上下文中,我们可以从技术上猜测“凡人”不是名词,通常它有时是名词。因此,is_noun在此示例中,标志为0/1/1,尽管理想情况下分别为0/0/1。

另外,目前,标记者更喜欢套叠标记。也就是说,当“有疑问”时,即。当lemmatizer报告给定的字词形式可以是名词或不是名词时,我们(尚未)分析概率,而是始终设置标记。

另一个棘手的问题是非字典形式的处理。从v.3.2开始,lemmatizer将所有此类预测报告为名词。

因此,请谨慎使用;这可能是一个嘈杂的信号。

is_number#

is_number当关键字的所有字符均为该[0-9]范围内的数字时,其标记为。不允许使用其他字符。

因此,例如,123将被标记is_number,但0.1230x123不会被标记。

要仔细研究这个特定示例,请注意,.默认情况下甚至不会将其解析为字符。因此,默认情况下charset_table,查询文本甚至不会产生单个关键字。取而代之的是,默认情况下,它会被标记为两个标记(关键字)0123,而这些标记又标记为is_number

查询级排名因素#

这些也许是最简单的因素。它们完全独立于要排名的文档;他们只描述查询。因此,它们仅在查询处理的开始就被计算一次。

has_digit_words#

查询级别,查询中的许多唯一has_digit关键字。重复项仅应处理一次。

is_latin_words#

查询级别,查询中的许多唯一is_latin关键字。重复项仅应处理一次。

is_noun_words#

查询级别,查询中的许多唯一is_noun关键字。重复项仅应处理一次。

is_number_words#

查询级别,查询中的许多唯一is_number关键字。重复项仅应处理一次。

max_lcs#

sum(lcs*user_weight)表达式可以采用的查询级别的最大可能值。这对于增重缩放很有用。例如,(旧式)MATCHANY排名公式使用此因子来确保任何单个字段中的完整短语匹配的排名都高于所有字段中部分匹配的任何组合的排名。

query_word_count#

查询级别,查询中的许多唯一和包含关键字。“包含”是指针对一些排除关键字进行了额外调整。例如,one one one one(one !two)查询都应为此因子分配一个值1,因为只有一个唯一的不排除关键字。

文档级排名因素#

这些是“看”查询和正在排序的(整个)匹配文档的一些因素。其中最有用的是经典BM系列因子的几种变体(如Okapi BM25)。

bm15#

文档级,经典BM15(1.2)价值的快速估算。它是在没有关键字出现过滤条件的情况下进行计算的(即,对所有术语发布而不只是匹配的术语)。此外,它会忽略文档和字段的长度。

例如,如果您搜索一个精确的短语,例如"foo bar"和,并且foobar关键字在文档中都出现10次,而该短语仅出现一次,则此bm15估算值仍将使用这两个关键字的TF(术语频率)值,即。记入所有词条(记帐),而不是“记帐”,而只算出1个实际匹配的记帐。

因此,bm15使用预先计算的文档TF,而不是即时计算实际匹配的TF。通过设计,当对整个文档运行简单的词袋查询时,这使所有零差。但是,一旦您开始使用几乎任何查询语法,它们之间的区别就会变得明显。

讨论一个问题,如果您将所有搜索都限制在一个字段中,并且查询条件是@title foo bar?权重是否真的取决于其他字段的内容,因为我们显然打算将搜索范围限制为标题?他们不应该。但是,随着bm15它们的近似。但这实际上只是性能与质量的权衡。

最后但并非最不重要的一点是bm25,直到v.3.0.2为止,这个因素在相当长一段时间内都没有正确地命名。(可以说,在某种程度上,它没有计算BM25值,一个非常具体k1 = 1.2b = 0情况。但来的。还有就是对于一个特别的名称b = 0家庭的情况下,它是bm15。)

bm25a()#

参数化的文档级文档BM25(k1,b)使用两个给定(必需)参数计算经典函数的值。例如:

SELECT ... OPTION ranker=expr('10000*bm25a(2.0, 0.7)')

与不同bm15,此因子仅在计算TF时考虑匹配的事件(发布)。它还需要index_field_lengths = 1将其设置为on才能计算当前和平均文档长度(这又是具有非零b参数的BM25函数所要求的)。

之所以调用它,是bm25a因为bm25最初(错误地)被BM25(1.2, 0)我们现在(正确)调用的那个值估计所误用bm15;在该a后缀中没有其他隐藏的含义。

bm25f()#

参数化的文档级文档BM25F(k1,b)使用两个给定的(必需的)参数以及一组额外的命名字段权重来计算扩展函数的值。例如:

SELECT ... OPTION ranker=expr('10000*bm25f(2.0, 0.7, {title = 3})')

与不同bm15,此因子仅在计算TF时考虑匹配的事件(发布)。它还需要index_field_lengths = 1将其设置为打开。

BM25F扩展使您可以为某些字段分配更大的权重。在内部,这些权重将只是简单地预先缩放TF,然后再将它们插入原始BM25公式中。有关原始TR的信息,请参见Zaragoza等人(1994年),“ TREC-13上的Microsoft Cambridge:Web和HARD轨道”一文。

doc_word_count#

文档级,在整个文档中匹配的许多唯一关键字。

field_mask#

文档级,匹配字段的32位掩码。在此掩码中将忽略数字为33或更高的字段。

领域级别的排名因素#

通常,字段级因子只是由排名引擎针对每个匹配的文档内文本字段计算的一些数值,关于当前查询,描述了实际匹配的这一方面。

由于查询可以匹配多个字段,但是最终权重需要为单个值,因此这些每个字段的值需要折叠为单个值。这意味着,与查询级别和文档级别的因素不同,您不能在排名公式中直接使用它们:

mysql> SELECT id, weight() FROM test1 WHERE MATCH('hello world')
OPTION ranker=expr('lcs');

ERROR 1064 (42000): index 'test1': field factors must only
occur within field aggregates in a ranking expression

正确的语法应使用聚合函数之一。允许多个不同的聚合:

mysql> SELECT id, weight() FROM test1 WHERE MATCH('hello world')
OPTION ranker=expr('sum(lcs) + top(max_idf) * 1000');

现在让我们更详细地讨论各个因素。

atc#

字段级别的汇总术语紧密度。这是一种基于接近度的度量,当文档包含更多组更靠近且更重要(稀有)的查询关键字时,该度量会越来越高。

警告:您应将ATC与OPTION idf='plain,tfidf_unnormalized'; 一起使用。否则您可能会得到意想不到的结果。

ATC的基本工作原理如下。对于文档中出现的每个关键字,我们都计算所谓的术语“紧密度”。为此,我们检查了所有查询关键字的所有其他最接近的出现位置(关键字本身也包括在内),位于主题出现的左侧和右侧。然后,我们k = pow(distance, -1.75)针对所有这些情况计算距离衰减系数,并对衰减后的IDF求和。因此,对于每个关键字的每次出现,我们都会获得一个“接近度”值,该值描述该关键字的“邻居”。然后,我们将这些每次出现的接近度乘以它们各自的主题关键字IDF,将它们全部求和,最后计算出该和的对数。

换句话说,我们处理文档中最佳(最接近)匹配的关键字对,并按距离系数缩放其IDF的乘积,计算成对的“接近度”:

pair_tc = idf(pair_word1) * idf(pair_word2) * pow(pair_distance, -1.75)

然后,我们对这种接近度求和,并计算最终的,对数衰减的ATC值:

atc = log(1 + sum(pair_tc))

请注意,此最终阻尼对数正是您应使用的原因OPTION idf=plain,因为如果没有该对数,则内部的表达式log()可能为负。

有接近关键字的出现实际上有利于更比ATC有更频繁的关键词。确实,当关键字彼此紧靠时,我们得到distance = 1k = 1;。当他们之间只有一个多余的词时,我们得到distance = 2k = 0.297;中间有两个额外的单词,我们得到distance = 3k = 0.146,依此类推。

同时,IDF的衰减有些慢。例如,在一百万个文档集中,在10、100和1000个文档中找到的3个示例关键字的IDF值分别为0.833、0.667和0.500。

因此,一个关键字对具有两个相当罕见的关键字,每个关键字仅出现在10个文档中,而关键字之间却有2个其他单词pair_tc = 0.101,因此,它们的收益几乎不超过一对100-doc和1000-doc关键字之间以及另一个关键字pair_tc = 0.099

此外,两个具有理想IDF且唯一之间只有3个单词的两个唯一的 1文档关键字对将获取a pair_tc = 0.088并输给两个彼此相邻的1000-doc关键字,并带有a pair_tc = 0.25

因此,基本上,尽管ATC确实结合了关键字频率和接近度,但它仍然非常重视接近度。

precision_field_hit#

字段级布尔值,查询是否(似乎)完全覆盖了当前字段,并且也按正确的(查询)术语顺序。

当字段基本上等于整个查询,或者等于丢掉几个术语的查询时,应设置此标志。请注意,术语顺序很重要,它也必须匹配。

例如,如果我们的查询为one two three,则或one two three,或one threetwo three应该全部具有exact_field_hit = 1,因为在这些示例中,所有字段关键字都由查询匹配,并且顺序正确。但是,由于错误的(非查询)字词顺序,three one应获取exact_field_hit = 0。然后,如果我们添加任何额外的术语,则one four three字段也应该为exact_field_hit = 0,因为four查询未将其匹配,即。此字段未完全涵盖。

另外,请注意停用词和其他文本处理工具可能会“打破”这一因素。

例如,当字段为one stop three,其中stop是停用词时,即使直觉上应该将其忽略,我们仍将得到0而不是1,并且该字段应等于,为此one three我们将得到1。怎么会?

这是因为停用词并未真正被完全忽略。它们仍然会影响排名(这是有意的,因此匹配的运算符和其他排名因子将按预期运行,就像在其他示例中一样)。

因此,该字段的索引为one * three,其中星号标记为跳过的位置。因此,当匹配one two three查询时,引擎知道位置1和3匹配得很好。但是,没有一种(有效的)方法可以知道原始字段中错位2的确切位置。即。是否有停用词,或查询中没有提及的任何常规词(如one four three示例中所示)。因此,在计算此因子时,我们发现存在一个不匹配的位置,因此我们假设该字段未完全覆盖(查询条件),并将该因子设置为0。

精确命中#

字段级别的布尔值,查询是否是整个当前字段的完全匹配和精确匹配的对象(即在归一化,形态等之后)。在SPH04排名中使用。

确切顺序#

字段级布尔值,是否所有查询关键字都以精确查询顺序在当前字段中匹配。(换句话说,我们的字段是否也以正确的顺序“覆盖”了整个查询。)

例如,(microsoft office)查询将exact_order = 1在包含We use Microsoft software in our office.内容的字段中产生。

但是,在包含(Our office is Microsoft free.)文本的字段中进行完全相同的查询会产生结果exact_order = 0,这是因为,尽管覆盖范围在那里(所有单词都匹配),但是顺序是错误的。

full_field_hit#

字段级别的布尔值,查询是否(似乎)完全覆盖了当前字段。

当查询匹配所有字段关键字时,应以任何顺序设置此标志。换句话说,此因素要求查询“完全覆盖”该字段,并“允许”重新排列单词。

例如,字段three onefull_field_hit = 1与query相对one two three。两个关键字都被“覆盖”(匹配),顺序无关紧要。

请注意,所有文档exact_field_hit = 1(甚至更为严格的文档)也必须获得full_field_hit = 1,反之则不然。

另外,请注意,停用词和其他文本处理工具可能会“破坏”这一因素,其原因与我们早些时候在exact_field_hit中讨论的原因完全相同

has_digit_hits#

字段一级的总匹配字段点击数仅计入has_digit关键字。

hit_count#

字段级总字段匹配数覆盖所有关键字。换句话说,在当前字段中匹配的关键字出现的总数。

请注意,一个关键字可能会多次出现(并匹配!)。例如,如果hello在一个字段中发生3次并且world发生5次,则为hit_count8。

is_noun_hits#

字段一级的总匹配字段点击数仅计入is_noun关键字。

is_latin_hits#

字段一级的总匹配字段点击数仅计入is_latin关键字。

is_number_hits#

字段一级的总匹配字段点击数仅计入is_number关键字。

生命周期#

字段级别的最长公共连续子序列。查询和文档之间最长的连续子短语的长度,以关键字计算。

LCCS因子与LCS相当相似,但从某种意义上讲,其限制性更大。尽管即使没有两个查询词彼此相邻匹配,LCS仍可能大于1,但如果文档中存在精确,连续的查询子短语,LCCS只会大于1 。

例如,one two three four five查询VS one hundred three hundred five hundred文件会产生lcs = 3,但是lccs = 1,因为即使3名匹配的关键字,虽然双方性格(onethree,和five)做查询和文档之间的匹配,没有出现次数的实际上是彼此相邻。

请注意,LCCS仍然无法区分频繁关键字和稀有关键字。为此,请参阅WLCCS因子。

生命科学#

场级最长公共子序列。这是文档和查询之间最大“常规”匹配的长度(以字为单位)。

通过构造,当字段中仅“杂散”关键字匹配时,其取最小值为1;而当整个查询在“原样”字段中匹配时,其取值的最大值为关键字中的查询长度的最大值。确切的查询顺序。

例如,如果查询为,hello world并且该字段在字段中的任何位置包含这两个单词作为子短语,lcs则将为2。另一个示例,这也适用于查询的子集,即。使用hello world program查询时,仅包含子hello world短语的字段也将获得lcs值2。

请注意,查询关键字的任何不连续子集在这里都可以使用,而不仅仅是相邻关键字的子集。例如,对于hello world program查询和hello (test program)字段内容,lcs也将为2,这是因为两者helloprogram匹配的位置与查询中相同。换句话说,查询和字段都在hello * program此处匹配不连续的2个关键字子集,因此的值为2 lcs

然而,如果我们保持了hello world program查询,但我们的领域来改变hello (test computer program),那么最长匹配子现在只有长1个关键字(两个子集实际上这里匹配,无论是helloprogram),并且lcs因此1。

最后,如果查询为,hello world program且该字段包含完全匹配hello world programlcs则将为3。(希望这并不奇怪。

max_idf#

字段级,max(idf)针对字段中匹配的所有关键字。

max_window_hits()#

参数化的字段级将max(window_hit_count)在所有N个关键字窗口(其中N是参数)上进行计算。例如:

mysql> SELECT *, weight() FROM test1 WHERE MATCH('one two')
    -> OPTION ranker=expr('sum(max_window_hits(3))');
+------+-------------------+----------+
| id   | title             | weight() |
+------+-------------------+----------+
|    1 | one two           |        2 |
|    2 | one aa two        |        2 |
|    4 | one one aa bb two |        1 |
|    3 | one aa bb two     |        1 |
+------+-------------------+----------+
3 rows in set (0.00 sec)

因此,在此示例中,我们查看的是较短的3关键字窗口,在3号文档中,相匹配的关键字相距太远,因此系数为1。但是,在4号文档中,该one one aa窗口出现了2次(即使只是一个关键字),因此此处的因子为2。1号和2号文件是直截了当的。

min_best_span_pos#

字段级,第一个最大LCS关键字范围的位置。

例如,假设我们的查询为hello world program,并且子hello world短语在当前字段中的位置13和21匹配了两次。现在假定helloworld还出现在该字段的其他位置(例如,位置5、8 和34),但是由于这些事件不是彼此相邻的,因此不算为子短语匹配。在此示例中,min_best_span_pos将为13,即。最长(最大)匹配的第一次出现的位置,即LCS。

请注意,对于单个关键字查询,如何min_best_span_pos必须始终相等min_hit_pos

min_gaps#

字段级,即字段中匹配的关键字之间(仅)之间的最小位置间隔数。少于2个关键字匹配时始终为0;否则为0。否则总是大于或等于0。

例如,使用相同的big wolf查询,big bad wolffield将产生min_gaps = 1; big bad hairy wolf田地将屈服min_gaps = 2; the wolf was scary and big田地将屈服min_gaps = 3; 但是,像i heard a wolf howl这样的字段会产生min_gaps = 0,因为在该字段中只有一个关键字会匹配,并且自然地,匹配关键字不会有空缺。

因此,这是一个相当低级的“原始”因素,您可能最希望在实际用于排名之前对其进行调整

具体调整主要取决于您的数据和所得公式,但是您可以从以下几点开始:

  • 在什么min_gaps时候可以忽略任何基础的提升word_count < 2;
  • 非平凡的min_gaps值(例如when word_count <= 2)可以用某个“最坏情况”常量限定,而平凡的值(例如when min_gaps = 0word_count < 2)可以用该常量代替;
  • 1 / (1 + min_gaps)可以应用类似的传递函数(这样,更好的min_gaps较小的值将使其最大化,而更糟的是,较大的min_gaps值将min_gaps缓慢下降)。

min_hit_pos#

字段级,第一个匹配的关键字出现的位置(以字为单位)。位置从1开始,因此min_hit_pos = 0在实际匹配的字段中必须不可能。

min_idf#

字段级,min(idf)针对字段中匹配的所有关键字(不出现!)。

sum_idf#

字段级,sum(idf)针对字段中匹配的所有关键字(不出现!)。

sum_idf_boost#

字段级,sum(idf_boost)针对字段中匹配的所有关键字(不出现!)。

tf_idf#

字段级,tf*idf该字段中所有匹配关键字的总和。(或者,自然是idf所有匹配的帖子的总和。)

记录TF为术语频率,即当前字段中(匹配的)关键字出现的次数。

IDF是逆文档频率,在0和1之间的浮点值,该值描述了该关键字如何频繁是在索引中。

基本上,经常出现的(因此并不十分有趣)的单词会获得较低的IDF,当所有索引文档中都包含关键字时,其最小值便会达到0。反之亦然,稀有,唯一且因此有趣的单词会获得较高的IDF,对于仅在单个文档中出现的唯一关键字,其IDF最高为1。

user_weight#

场级,用户指定的每场权重(有关如何设置这些权重的更多详细信息,请参阅本OPTION field_weights节)。默认情况下,所有这些权重都设置为1。

图书馆#

场级加权最长公共连续子序列。当前查询和字段之间最长连续子短语的关键字上IDF的总和。

WLCCS的计算与LCCS非常相似,但是每次出现“适当”的关键字时,关键字IDF都会增加它,而不是仅将其增加1(LCS和LCCS都是这种情况)。这样我们就可以将较稀有和重要的关键字序列比频繁的关键字序列排名更高,即使后者更长。例如,查询实际上Zanzibar bed and breakfastlccs = 1针对某个hotels of Zanzibar字段,但lccs = 3针对某个London bed and breakfast字段,即使Zanzibar实际上可能比整个bed and breakfast短语都少见。通过考虑关键字频率,WLCCS因子在一定程度上有所缓解。

字数#

字段级,字段中匹配的唯一关键字的数量。例如,如果hello和都world出现在当前字段中,则为word_count2,而不管两个关键字出现多少次。

排名:内置的排名公式#

所有内置的Sphinx排名程序都可以使用基于表达式的排名程序进行模拟。您只需要使用OPTION ranker子句传递适当的公式即可。

当然,这种仿真要比使用内置的预编译排名工具慢。但是,如果您想从现有的内置基准排名工具开始微调排名公式,可能仍然会很感兴趣。(此外,这些公式还以易于阅读的方式定义了精确的内置排名器细节。)

等级
PROXIMITY_BM25 sum(lcs*user_weight)*1000 + bm25
BM25 bm25
没有 1
字数 sum(hit_count*user_weight)
接近 sum(lcs*user_weight)
马昌 sum((word_count + (lcs - 1)*max_lcs)*user_weight)
现场面具 field_mask
SPH04 sum((4*lcs + 2*(min_hit_pos==1) + exact_hit)*user_weight)*1000 + bm25

这里是一个完整的示例查询:

SELECT id, weight() FROM test1
WHERE MATCH('hello world')
OPTION ranker=expr('sum(lcs*user_weight)*1000 + bm25')

排名:IDF魔法#

Sphinx支持几种不同的IDF(反向文档频率)计算选项。当您处于以下状态时,这些因素可能会影响您的相关性排名(也称为得分):

  • 甚至使用内置的排名工具分片数据;
  • 进行任何自定义排名工作,即使是在单个分片上也是如此。

默认情况下,术语IDF是(a)每个分片,并且(b)是在线计算的。因此,它们在排名时可能会大幅波动。而且还有其他一些排名因素依赖于它们,因此整个排名可能会以看似随机的方式发生很大变化。原因是双重的。

首先,IDF通常在各个分片上有所不同(即组成更大组合索引的各个索引)。这意味着完全相同的文档可能会根据其最终所在的特定分片而排名不同。

其次,随着您更新索引数据,IDF可能因查询而异。时间上的不稳定可能会或可能不会达到预期的效果。

为了缓解这些怪癖(如果它们影响了您的用例),Sphinx提供了两个功能:

  1. local_df 聚合分片IDF的选项。
  2. global_idf 强制执行预构建的静态IDF的功能。

local_df语法是SELECT ... OPTION local_df=1,启用该选项将告诉查询(更准确)计算IDF(即在整个索引上而不是单个分片上)。出于性能原因,默认值为0(关闭)。

global_idf 功能更加复杂,包括以下几个组成部分:

  • indextool --dumpdict --stats 生成源数据的开关,即每个分片的字典转储;
  • indextool --buildidf 从这些文件构建静态IDF文件的开关;
  • 每个分片的global_idf配置指令,可让您将静态IDF文件分配给分片;
  • 每个查询OPTION global_idf=1强制查询使用该文件。

这两个功能都会影响用于IDF计算的输入变量。进一步来说:

  • nDF,文件频率(给定期限);
  • N文集大小,文档总数;
  • 默认情况下,n和和N均为分片;
  • 通过local_df,它们都在分片上求和;
  • 使用global_idf,它们都来自静态IDF文件。

静态global_idf文件实际上n为每个术语存储了一堆值,以及N整个语料库的值,这些值是在--buildidf阶段中所有可用的源文件中求和的。对于静态global_idf文件中不存在的术语,将使用其当前(动态)DF值。local_df还应该影响那些。

为避免溢出,请N根据实际语料库大小进行调整。这意味着,例如,如果global_idf文件说有1000个文档,但是索引携带3000个文档,则将N其设置为较大的值,即3000。因此,您应该避免对字典转储使用太小的数据切片,和/或手动调整频率,否则您的静态IDF可能就不足够了。

为使global_idf文件合理压缩,可以--skip-uniq在执行--buildidf阶段时使用附加开关。该开关将过滤掉仅出现一次的所有术语。这通常会.idf大大减小文件大小,同时仍会产生准确或几乎精确的结果。

Sphinx如何计算IDF#

从v.3.3开始,我们删除了几种传统的IDF计算方法,现在Sphinx始终使用以下公式从n(文档频率)和N(语料库大小)计算IDF 。

  • idf = log(N/n) / (2*log(N+1)) * term_idf_boost

因此,我们从实际标准开始raw_idf = log(N/n);然后将其标准化为语料库大小;并进一步压缩idf[0.0, 0.5)范围内。

然后,我们将根据查询应用每学期的用户提升(如果有)。term_idf_boost默认值为,1.0但可以使用相应的修饰符(例如)针对各个查询词任意更改。... WHERE MATCH('cat^1.2 dog')

作为记录,此IDF规范化本身和特定[0,0.5)范围现在大部分都是历史性的。考虑将规范化删除。

排名:用 rank_fields#

当索引和查询包含任何特殊的“假”关键字(通常用于加快匹配速度)时,将那些排除在排名之外是有意义的。可以通过将此类关键字放入特殊字段中,然后OPTION rank_fieldsSELECT语句中使用子句来选择具有实际文本的字段进行排名来实现。例如:

SELECT id, weight(), title FROM myindex
WHERE MATCH('hello world @sys _category1234')
OPTION rank_fields='title content'

rank_fields被设计为如下工作。计算排名因子时,仅处理排名字段中的关键字出现。其他任何出现都将被忽略(即,通过排名)。

请注意以下几点:对于查询级别的因素,只能分析查询本身,而不能分析索引数据。

这意味着当您未在查询中显式指定字段时,查询解析器必须假定关键字实际上可以出现在文档中的任何位置。并且,例如,MATCH('hello world _category1234')query_word_count=3基于此原因进行计算。此查询确实有3个关键字,即使_category1234从来没有真正的任何地方发生,除了sys现场。

除此之外,rank_fields非常简单。匹配仍将照常进行。但是出于排名目的,“系统”字段中的任何事件(命中)都可以忽略和隐藏。

操作:“围困模式”,临时全局查询限制#

Sphinx searchd现在具有所谓的“围困模式” ,可以在给定的时间内临时对所有传入SELECT查询施加服务器范围的限制。当某些客户端searchd大量请求泛滥,而无论出于何种原因,在其他级别停止这些请求很复杂时,这将非常有用。

围攻模式是通过一些全局服务器变量控制的。下面的示例将引入15秒钟的围困模式,并对每个查询施加最多1000个处理过的文档和最多0.3秒(挂钟)的限制:

set global siege=15
set global siege_max_fetched_docs=1000
set global siege_max_query_msec=300

一旦超时达到零,围困模式将自动解除。

还存在一些您无法更改的硬编码限制,即:

  • 上限为siege300秒,即5分钟
  • 上限siege_max_fetched_docs是1,000,000个文档
  • 上限为siege_max_query_msec1秒,即1000毫秒

请注意,当围困停止时,将重置当前的围困限制。因此,在上面的示例中,如果您在20秒内开始另一次围攻,那么下一次围攻将以1M docs和1000毫秒的限制重新开始,而不是前一个文档的1000 docs和300毫秒的限制重新开始。

可以通过将超时归零来随时关闭攻城模式:

set global siege=0

当前的围攻状态报告于SHOW STATUS

mysql> show status like 'siege%';
+------------------------+---------+
| Counter                | Value   |
+------------------------+---------+
| siege_sec_left         | 296     |
| siege_max_query_msec   | 1000    |
| siege_max_fetched_docs | 1000000 |
+------------------------+---------+
3 rows in set (0.00 sec)

下一个事务,文档限制有几个有趣的细节,需要解释。

首先,fetched_docs对于术语搜索和非术语搜索,计数器的计算方式有所不同。对于术语搜索,它将逐批统计全文术语阅读器获取的所有(非唯一!)行。对于非术语搜索,它将对所有匹配的(唯一的)活动行进行计数(通过读取属性索引或通过完全扫描)。

其次,对于多索引搜索,siege_max_fetched_docs限制将按本地索引(碎片)划分,并按其文档数加权。

如果您真的很好奇,让我们更详细地讨论这些内容。

非术语搜索的情况很容易。首先将检查所有实际存储的行(无论是来自完整扫描还是来自属性索引读取),然后在fetched_docs计数器中进行核算,然后进行进一步处理(使用额外的计算,过滤器等)。最重要的是,以这种方式限制的查询将在最多N个行上运行“硬”计算,过滤器检查等。因此,在最佳情况下(如果所有WHERE过滤器均通过),查询将返回N行,甚至再也不会返回一行。

现在,术语搜索案例更加有趣。最低级别的术语阅读器也会发出单独的行,但是与“扫描”情况相反,术语或行可能会重复。该fetched_docs计数器计数仅仅发出的那些行,因为它需要限制完成工作的总量。因此,例如,对于像2个词这样(foo bar)的查询,当两个从全文索引中获取N个文档时,该处理将停止……即使尚未匹配单个文件!例如,如果某个术语重复出现(例如在(foo foo)查询中),那么这两种情况都会对计数器产生影响。因此,对于具有M个必填项的查询,所有条件项都进行AND运算,则匹配项的文档应大致等于N / M,因为在每个术语阅读器中,每个匹配的文档都会被算作“已处理” M次。因此,一个(foo bar)或一个(foo foo)示例查询的限制为1000,应该会导致大约500个匹配结果。

上面的“大约”意味着偶尔可能会有更多的比赛。由于性能原因,术语“阅读器”是分批工作的,实际fetched_docs计数器可能会比施加的限制稍大,最大为批大小。但这必须是微不足道的,因为仅处理一个小批量就非常快。

至于在索引之间分配限制,它只是基于每个索引文档数的按比例分配。例如,假设将siege_max_fetched_docs其设置为1000,并且您的查询中有2个本地索引,一个包含1400K文档,一个包含600K文档。(这是直接引用还是通过分布式索引都无所谓。)然后,每个索引的限制将分别设置为700和300个文档。简单。

最后但并非最不重要的一点是,请注意,“攻城模式”的全部要点是针对过于复杂的搜索有意使搜索结果降级!使用时要格外小心;本质上仅用它来扑灭无法通过其他任何方式迅速缓解的集群火灾;在这一点上,我们建议只曾经手动使用它。

SphinxQL参考#

本部分最终应包含完整的SphinxQL参考。如果您要查找的语句尚未在此处记录,请参阅旧版SphinxQL v.2.x参考文档。

这是SphinxQL语句的完整列表。

批量更新语法#

BULK UPDATE ftindex (id, col1 [, col2 [, col3 ...]]) VALUES
(id1, val1_1 [, val1_2 [, val1_3 ...]]),
(id2, val2_1 [, val2_2 [, val2_3 ...]]),
...
(idN, valN_1 [, valN_2 [, valN_3 ...]])

BULK UPDATE使您可以使用单个语句更新多行。与运行N条单独的语句相比,批量更新提供了更简洁的语法和更好的性能。

总体而言,它们与常规更新非常相似。快速总结:

  • 您可以更新(整个)属性,自然保留它们的类型(即使更改宽度,即更新字符串或整个JSON等时);
  • 您可以在JSON中更新数值,还可以保留其类型(并自然保留宽度)。

列表中的第一列必须始终是该id列。行由文档ID唯一标识。

与常规UPDATE查询一样,其他要更新的列也可以是常规属性或单个JSON键。这是几个例子:

BULK UPDATE test1 (id, price) VALUES (1, 100.00), (2, 123.45), (3, 299.99)
BULK UPDATE test2 (id, json.price) VALUES (1, 100.00), (2, 123.45), (3, 299.99)

支持所有值类型(数字,字符串,JSON,MVA)。

现有值的批量更新必须保留类型。这是对常规属性的自然限制,但也适用于JSON值。例如,如果您使用浮点数更新整数JSON值,则该浮点数将被转换(截断)为当前整数类型。

将会发生兼容的值类型转换。允许截断。

不兼容的转换将失败。例如,字符串将不会自动转换为数值。

尝试更新不存在的JSON密钥将失败。

KILL语法#

KILL <thread_id>
KILL SLOW <min_msec> MSEC

KILL 使您可以根据线程ID或当前运行时间强行终止长时间运行的语句。

对于第一个版本,您可以使用SHOW THREADS语句获取线程ID 。

请注意,被强行终止的查询将几乎就像完成了OK一样返回,而不是引发错误。他们将返回到目前为止累积的部分结果集,并发出“查询被杀死”警告。例如:

mysql> SELECT * FROM rt LIMIT 3;
+------+------+
| id   | gid  |
+------+------+
|   27 |  123 |
|   28 |  123 |
|   29 |  123 |
+------+------+
3 rows in set, 1 warning (0.54 sec)

mysql> SHOW WARNINGS;
+---------+------+------------------+
| Level   | Code | Message          |
+---------+------+------------------+
| warning | 1000 | query was killed |
+---------+------+------------------+
1 row in set (0.00 sec)

各个网络连接都不会被强制关闭。

目前,可以被杀死的唯一陈述SELECTUPDATEDELETEKILL将来可能会开始支持其他语句类型。

在这两个版本中,KILL通过受影响的行数返回标记为终止的线程数:

mysql> KILL SLOW 2500 MSEC;
Query OK, 3 row affected (0.00 sec)

已经标记的线程将不会再次标记并以这种方式报告。

<min_msec>第二个版本的参数没有限制,因此KILL SLOW 0 MSEC是完全合法的语法。该特定语句将终止所有当前正在运行的查询。因此,请小心使用。

SELECT语法#

SELECT <expr> [[AS] <alias>] [, ...]
FROM <ftindex> [, ...]
    [{USE | IGNORE | FORCE} INDEX (<attr_index> [, ...]) [...]]
[WHERE
    [MATCH('<text_query>') [AND]]
    [<where_condition> [AND <where_condition> [...]]]]
[GROUP [<N>] BY <column> [, ...]
    [WITHIN GROUP ORDER BY <column> {ASC | DESC} [, ...]]
    [HAVING <having_condition>]]
[ORDER BY <column> {ASC | DESC} [, ...]]
[LIMIT [<offset>,] <row_count>]
[OPTION <opt_name> = <opt_value> [, ...]]
[FACET <facet_options> [...]]

SELECT是主要的查询主力,因此具有相当广泛(也许有点复杂)的语法。该语法有许多不同的部分(aka子句)。幸运的是,它们大多数都是可选的。

简而言之,它们如下:

  • 必填SELECT列列表(又名项目列表,又名表达式列表)
  • 必需FROM子句,带有全文本索引列表
  • 可选<hint> INDEX子句,带有属性索引用法提示
  • 可选WHERE条件子句,具有行过滤条件
  • 可选GROUP BY子句,具有行分组条件
  • 可选ORDER BY子句,具有行排序条件
  • 可选LIMIT子句,其结果集的大小和偏移量
  • 可选OPTION子句,带有所有特殊选项
  • 可选FACET子句,带有请求的其他构面的列表

与常规SQL最显着的区别是:

  • FROMlist 不是隐式的JOIN,而是更像一个UNION
  • ORDER BY 始终存在,默认为 ORDER BY WEIGHT() DESC, id ASC
  • LIMIT 始终存在,默认为 LIMIT 0,20
  • GROUP BY 总是选择一个特定的“最佳”行来代表该组

索引提示子句#

出于性能或调试方面的原因,索引提示可用于调整查询优化器的行为和属性索引的使用。请注意,通常您不必使用它们。

可以使用多个提示,并且可以按任意顺序列出多个属性索引。例如,以下语法是合法的:

SELECT id FROM test1
USE INDEX (idx_lat)
FORCE INDEX (idx_price)
IGNORE INDEX (idx_time)
USE INDEX (idx_lon) ...

<hint> INDEX子句的所有形式都将索引列表作为其参数,例如:

... USE INDEX (idx_lat, idx_lon, idx_price)

总之,提示是这样工作的:

  • USE INDEX 限制优化器仅使用给定索引的子集;
  • IGNORE INDEX 严格禁止使用给定的索引;
  • FORCE INDEX 严格强制使用给定的索引。

USE INDEX告诉优化器它必须只考虑给定的索引,而不是所有适用的索引。换句话说,在没有该USE子句的情况下,所有索引都是公平的。在它的存在下,只有USE列表中提到的那些存在。优化器仍会决定是实际使用还是忽略任何特定索引。在上面的示例中,它仍然可以选择idx_lat仅使用,但绝不能使用idx_time,因为它没有明确提及。

IGNORE INDEX完全禁止优化器使用给定的索引。忽略优先,它们同时覆盖USE INDEXFORCE INDEX。因此,尽管合法USE INDEX (foo, bar) IGNORE INDEX (bar),但它太冗长了。简单USE INDEX (foo)实现完全相同的结果。

FORCE INDEX 使优化器强行使用给定的索引(也就是说,如果它们完全适用),而不管查询成本如何估算。

有关属性索引和提示的更多讨论和详细信息,请参阅“使用属性索引”

显示索引代理状态语法#

SHOW INDEX <distindex> AGENT STATUS [LIKE '...']

SHOW INDEX AGENT STATUS 使您可以检查与给定分布式索引中的每个代理(然后是代理的每个镜像主机)关联的内部每个代理计数器。

代理按配置顺序编号。每个代理中的镜像也按配置顺序编号。所有计时器在内部必须具有微秒精度,但应以浮点数和毫秒数显示,例如:

mysql> SHOW INDEX dist1 AGENT STATUS LIKE '%que%';
+--------------------------------+-------+
| Variable_name                  | Value |
+--------------------------------+-------+
| agent1_host1_query_timeouts    | 0     |
| agent1_host1_succeeded_queries | 1     |
| agent1_host1_total_query_msec  | 2.943 |
| agent2_host1_query_timeouts    | 0     |
| agent2_host1_succeeded_queries | 1     |
| agent2_host1_total_query_msec  | 3.586 |
+--------------------------------+-------+
6 rows in set (0.00 sec)

从输出中可以看到,自searchd启动以来,仅向每个代理发送了1个查询,该查询在两个代理上都运行良好,分别花费了大约2.9 ms和3.6 ms。特定的代理程序地址是有意不在此状态输出中的一部分,以避免混乱;然后可以使用以下DESCRIBE语句检查它们:

mysql> DESC dist1
+---------------------+----------+
| Agent               | Type     |
+---------------------+----------+
| 127.0.0.1:7013:loc1 | remote_1 |
| 127.0.0.1:7015:loc2 | remote_2 |
+---------------------+----------+
2 rows in set (0.00 sec)

在这种情况下(即没有镜像),映射很简单,我们可以看到我们只有两个代理,分别agent1在端口7013和agent2端口7015上,并且我们现在知道哪些统计信息确切地与哪个代理相关联。简单。

显示变量语法#

SHOW [{GLOBAL | SESSION}] VARIABLES
    [{WHERE variable_name='<varname>' [OR ...] |
    LIKE '<varmask>'}]

SHOW VARIABLES 语句有两个非常不同的目的:

  • 提供与第三方MySQL客户端的兼容性;
  • 检查searchd服务器变量的当前状态。

需要兼容模式才能支持来自某些MySQL客户端的连接,这些客户端会自动SHOW VARIABLES在连接上运行,并且在该语句引发错误时失败。

可选GLOBALSESSION作用域条件仅是为了兼容性,目前,作用域将被忽略。始终显示所有变量,包括全局变量和每个会话变量。

WHERE variable_name ... 条件也仅出于兼容性考虑,也被忽略。

LIKE '<varmask>' 条件受支持且可以正常运行,例如:

mysql> show variables like '%comm%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | 1     |
+---------------+-------+
1 row in set (0.00 sec)

显示的大多数变量SHOW VARIABLES都是可变的(即可以随时更改)。有些是只读的。有些是不变的。

mysql> show variables;
+--------------------------+-----------------------------+
| Variable_name            | Value                       |
+--------------------------+-----------------------------+
| attrindex_thresh         | 1024                        |
| autocommit               | 1                           |
| character_set_client     | utf8                        |
| character_set_connection | utf8                        |
| collation_connection     | libc_ci                     |
| log_debug_filter         |                             |
| log_level                | info                        |
| max_allowed_packet       | 8388608                     |
| net_spin_msec            | 10                          |
| qcache_max_bytes         | 0                           |
| qcache_thresh_msec       | 3000                        |
| qcache_ttl_sec           | 60                          |
| query_log_format         | sphinxql                    |
| query_log_min_msec       | 0                           |
| siege                    | 0                           |
| siege_max_fetched_docs   | 1000000                     |
| siege_max_query_msec     | 1000                        |
| sql_fail_filter          |                             |
| sql_log_file             |                             |
| version                  | 3.3.1-dev (commit fc5fb556) |
+--------------------------+-----------------------------+
20 rows in set (0.00 sec)

例如,log_level是可变的;max_allowed_packet是只读的(可以更改sphinx.conf但可以重新启动);并且version是恒定的。

具体的基于变量的文档可以在“服务器变量参考”部分中找到。

功能参考#

本节最终应包含有关在SELECT其他适用地方支持的功能的完整参考。如果您要查找的功能尚未在此处记录,请参阅旧版Sphinx v.2.x表达式参考文档。

这是内置的Sphinx函数的完整列表。

DOT() 功能#

DOT(vector1, vector2)
vector = {json.key | array_attr | FVEC(...)}

DOT() 函数在两个向量参数上计算点积。

向量可以从JSON或数组属性中获取,也可以使用FVEC()函数指定为常量。所有组合通常都应该起作用。

结果类型始终是float为了保持一致性和简单性。(根据我们的基准,在适用情况下,使用integerbigint针对结果类型获得的性能提升几乎是不存在的。)

请注意,无论如何,内部计算仍针对特定的输入参数类型进行了优化。例如,int8VS int8载体应该是相当明显快于floatdouble包含相同的数据,这既是因为整数乘法是不太昂贵的,而且由于矢量int8将利用6×更少的内存。

因此,根据经验,请使用尽可能窄的类型,这会带来更好的RAM使用率和更好的性能。

当其中一个参数为NULL或不是数字矢量时(对于JSON来说很可能会发生这种情况),或者当两个参数都是不同大小的矢量时,则DOT()返回0。

FVEC() 功能#

FVEC(const1 [, const2, ...])
FVEC(json.key)

FVEC()函数可让您定义浮点向量。当前的两个用例是:

  • 定义一个常数向量,以供以后使用 DOT()
  • 将存储在JSON中的优化浮点向量传递给UDF

常数向量形式。

在第一种形式中,参数是数字常量的列表。请注意,在此处使用整数还是浮点数可能会有区别!

当to的两个参数DOT()都是整数向量时,DOT()可以使用优化的整数实现,并使用定义此类向量FVEC(),您仅应使用整数。

向量的经验法则通常是:仅使用尽可能窄的类型。因此,可能会引入额外的优化。相反,它们肯定不会。

例如,允许优化器FVEC(1,2,3,4)从整数扩展到浮点数,这不足为奇。现在,在这种情况下,还可以float在适用的情况下将结果向量缩小为整数,因为我们可以知道所有原始值在扩大之前都是整数。

FVEC(1.0, 2.0, 3.0, 4.0)严格禁止从浮点形式缩小到整数。因此,即使值实际上是相同的,在第一种情况下也可以进行仅整数优化,而在第二种情况下则不能。

UDF参数包装形式。

在第二种形式中,唯一的参数必须是JSON密钥,并且输出仅用于UDF函数(因为否则FVEC()不需要此包装器,而您只使用密钥本身即可)。将检查关联的值类型,包装优化的浮点向量并将其传递给UDF,然后在UDF调用中将任何其他类型替换为空向量(零长度且没有数据指针)。各自的UDF类型为SPH_UDF_TYPE_FLOAT_VEC

注意,这种情况是有意设计为UDF的快速访问器,它仅将float向量传递给它们,并避免了任何数据复制和转换。

因此,如果您尝试包装并传递其他任何内容,则将null向量传递给UDF。可以是具有不同类型数值的通用混合向量,可以是优化int8向量,可以是double向量-但在所有这些情况下,尽管它们是兼容的并且可以从技术上转换为某些临时float向量然后向下传递,这种转换就不会发生。出于性能原因,故意。

PP() 功能#

PP(FACTORS())
PP(jsoncol.key)
PP(jsoncol.key)

PP()function pretty-prints JSON输出(默认情况下将紧凑而不是美化)。应用于FACTORS()函数时,它还将自动强制JSON输出。例如:

mysql> select id, j from lj limit 1 \G
*************************** 1. row ***************************
id: 1
 j: {"gid":1107024,"urlcrc":2557061282}
1 row in set (0.01 sec)

mysql> select id, pp(j) from lj limit 1 \G
*************************** 1. row ***************************
   id: 1
pp(j): {
  "gid": 1107024,
  "urlcrc": 2557061282
}
1 row in set (0.01 sec)

mysql> select id, factors() from lj where match('hello world')
    -> limit 1 option ranker=expr('1') \G
*************************** 1. row ***************************
       id: 5332
factors(): bm15=735, bm25a=0.898329, field_mask=2, doc_word_count=2,
field1=(lcs=2, hit_count=2, word_count=2, tf_idf=0.517828, ...
1 row in set (0.00 sec)

mysql> select id, pp(factors()) from lj where match('hello world')
    -> limit 1 option ranker=expr('1') \G
*************************** 1. row ***************************
       id: 5332
pp(factors()): {
  "bm15": 735,
  "bm25a": 0.898329,
  "field_mask": 2,
  "doc_word_count": 2,
  "fields": [
    {
      "field": 1,
      "lcs": 2,
      "hit_count": 2,
      "word_count": 2,
      ...
1 row in set (0.00 sec)

切片功能#

SLICEAVG(json.key, min_index, sup_index)
SLICEMAX(json.key, min_index, sup_index)
SLICEMIN(json.key, min_index, sup_index)
函数调用示例 信息
SLICEAVG(j.prices, 3, 7) 计算切片中的平均值
SLICEMAX(j.prices, 3, 7) 计算切片中的最小值
SLICEMIN(j.prices, 3, 7) 计算切片中的最大值

切片函数(SLICEAVGSLICEMAXSLICEMIN)期望将JSON数组作为第一个参数,并将两个常量整数索引A和B分别作为第二个参数和第三个参数。然后,它们在各个切片中的数组元素上计算一个聚合值,即从包含索引A的索引到包含索引B的排斥(就像Python和Golang中一样)。例如,在上面的示例中,将处理元素3、4、5和6,但不处理元素7。索引当然是基于0的。

float即使所有输入值实际上都是整数,返回的值也是。

具有非数字项的非数组和切片将返回值0.0NULL最终可能会更改)。

STRPOS() 功能#

STRPOS(haystack, const_needle)

STRPOS()返回其第二个参数(“ needle”)在其第一个参数(“ haystack”)中第一次出现的索引,或者-1如果没有出现,则返回该索引。

索引以字节计(而不是Unicode代码点)。

此刻,针必须是恒定的线。如果needle是一个空字符串,那么将返回0。

服务器变量参考#

searchd有许多服务器变量,可以使用该SET GLOBAL var = value语句即时更改。本节提供所有这些变量的参考。

attrindex_thresh 变量#

SET GLOBAL attrindex_thresh = 256

启用构建属性索引所需的最小段大小,以行计。默认值为1024。

Sphinx将仅为“足够大”的段(即那些RAM或磁盘段)创建属性索引。作为推论,如果整个FT指数足够小,即 在此阈值以下,将完全不使用属性索引。

目前,此设置似乎仅对测试和调试有用,并且通常不必在生产中进行调整。

log_debug_filter 变量#

SET GLOBAL log_debug_filter = 'ReadLock'

禁止以给定前缀开头的调试级日志条目。默认为空字符串,即。不要隐藏任何条目。

这样可以searchd减少debug较高log_level级别的闲聊次数。

目前,此设置似乎仅对测试和调试有用,并且通常不必在生产中进行调整。

log_level 变量#

SET GLOBAL log_level = {info | debug | debugv | debugvv}'

设置当前的日志记录级别。默认(最低)级别是info

该变量对于临时启用searchd具有此详细级别的调试登录很有用。

目前,此设置似乎仅对测试和调试有用,并且通常不必在生产中进行调整。

net_spin_msec 变量#

SET GLOBAL net_spin_msec = 30

在网络线程中设置轮询器旋转周期。默认值为10毫秒。

通常的线程CPU切片基本上在5到10毫秒的范围内。(对于真正好奇的人,一个很好的起点是在kernel/sched/fair.c源代码中提到“目标抢占等待时间”和“最小抢占粒度”的行。)

因此,如果一个重负载的网络线程epoll_wait()仅用看似很小的1毫秒的超时就进行了调用,则该线程有时可能会被抢占并浪费宝贵的微秒。根据古老的内部基准,这些天我们既不能轻易地复制也不能否认(换句话说:在某些情况下),这可能会导致很大的差异。更具体地说,内部注释报告不旋转时约为3000 rps(即使用net_spin_msec = 0),而旋转时约为5000 rps。

因此,默认情况下,我们选择在epoll_wait()的持续时间内以零超时进行调用net_spin_msec,以使我们的网络线程“实际”分片接近于10毫秒,以防万一我们收到大量传入查询。

query_log_format 变量#

SET GLOBAL query_log_format = {plain | sphinxql}

即时更改搜索查询日志记录格式。默认值为plain,另一个选项为sphinxql

query_log_min_msec 变量#

SET GLOBAL query_log_min_msec = 1000

更改要记录的搜索查询的最小经过时间阈值。默认值为0毫秒,即。记录所有查询。

sql_fail_filter 变量#

SET GLOBAL sql_fail_filter = 'insert'

“失败过滤器”是对所有传入的SphinxQL查询强加的简单早期过滤器。与给定的非空子字符串匹配的任何传入查询都将立即失败并显示错误。

就像攻城模式一样,这对于紧急维护非常有用。这两种机制彼此独立,即。故障过滤器和围困模式都可以同时打开。

从v.3.2开始,匹配非常简单,区分大小写和按字节排列。将来这可能会改变。

要删除过滤器,请将值设置为空字符串。

SET GLOBAL sql_fail_filter = ''

sql_log_file 变量#

SET GLOBAL sql_log_file = '/tmp/sphinxlog.sql'

SQL日志使您(临时)启用(几乎)原始格式的所有传入SphinxQL查询的日志记录。与query_log指令相比,此记录器:

  • 记录所有 SphinxQL查询,而不仅仅是搜索;
  • 没有记录任何SphinxAPI电话;
  • 对性能没有明显影响;
  • 默认情况下停止。

查询存储为已接收。; /* EOQ */每次查询后都会存储一个硬编码的分隔符,然后存储一个换行符,以方便解析。捕获并稍后重播所有客户端SphinxQL查询流非常有用。

出于性能原因,SQL日志记录使用了相当大的缓冲区(达到几兆字节),因此tail在启动此日志后没有立即显示内容时,请不要发出警报。

要停止SQL日志记录(并关闭并刷新日志文件),请将值设置为空字符串。

SET GLOBAL sql_log_file = ''

我们建议长时间在加载的系统上保持SQL登录,因为它可能会占用大量磁盘空间。

3.x的变化#

版本3.3.1,2020年7月6日#

新功能:

  • 添加了UDF调用批处理功能,使UDF可以一次处理多个匹配的行
  • PP()FACTORS()和JSON值添加了漂亮的打印功能
  • 添加了多线程索引加载
  • 添加了KILLSphinxQL语句
  • 添加了SHOW INDEX AGENT STATUSSphinxQL语句,并从此处移动了每个代理的计数器SHOW STATUS

次要新增功能:

  • 增加了一些运行时的

服务器变量

SHOW VARIABLES

,即

  • log_debug_filternet_spin_msecquery_log_min_msecsql_fail_filter,和sql_log_file
  • 移动attrindex_threshsiege_max_fetched_docssiege_max_query_msecqcache_max_bytesqcache_thresh_msec,和qcache_ttl_secSHOW STATUS

  • 增加了SET GLOBAL server_varsphinxql_state启动脚本的支持

变更和改进:

  • 删除了对timestamp列的支持,请改用uint类型(仍然支持现有索引;timestamp应自动按照uint这些条件工作)
  • 删除OPTION idf和统一的IDF计算,请参阅“ Sphinx如何计算IDF”
  • WEIGHT()从整数更改为浮点
  • global_idf行为改变;现在缺少的条款获取本地IDF而不是零
  • 更改OPTION cutoff为正确说明所有已处理的匹配项
  • 将v.3.1和更早版本中不建议使用的指令更改为硬错误
  • 优化索引(快约1-2%)
  • DOT()int8向量上进行了优化,速度提高了1.3倍
  • 在高达350+ Krps的快速只读查询上优化了查询吞吐量(各种内部锁定和性能变化,也称为“高负载优化”)
  • 改进的浮点值格式,主要在SphinxQL输出中
  • 改进了UPDATE处理方式,现在可以并行执行更新(再次)
  • 改进的索引架构检查(更多检查无效名称等)
  • 增加SHOW THREADS从512至2048字节查询限制

修正:

  • 修复了使用FACTORS()参数时的UDF内存泄漏,并对该情况进行了一些优化
  • sql_log_file在高查询负载下导致(稀有)崩溃的固定种族
  • 修复了带有表达式的构面偶尔会产生丢失或不正确的结果行的问题
  • 修复了docid哈希值溢出(在相当大的索引上触发)
  • 修复了CALL KEYWORDSglobal_idf查询中未使用标准化术语的问题
  • 进行int / float const混合推广时的固定表达类型问题
  • 修复了RAM段未考虑docid哈希大小的问题
  • 修复了INSERT仅检查RAM段中重复的docid的问题
  • 修复了COUNT(*)vs空RT 的内部错误

版本3.2.1,2020年1月31日#

新功能:

  • 例如,为适当的查询级同义词添加了term-OR运算符(red || green || blue) pixel

  • 例如,添加了仅文档的字形!indexme => differently

  • 添加了一些

矢量搜索

改进

  • 添加了int8 / int / float固定宽度数组属性支持,例如sql_attr_int8_array = myvec[128]
  • 增加了DOT()对所有这些新数组类型的支持
  • 为JSON int8[]JSON语法扩展添加了int8向量支持float[]
  • 增加了FVEC(json.field)对表达式的支持,以及SPH_UDF_TYPE_FLOAT_VEC对UDF 的相应支持

  • 添加了BULK UPDATESphinxQL语句

  • 增加了针对多个GEODIST-OR查询的属性索引读取,速度提高了15倍以上(有关详细信息,请参见地理搜索部分)

  • 添加了攻城模式,临时全局查询限制为SET GLOBAL siege

  • 加入sum_idf_boostis_noun_hitsis_latin_hitsis_number_hitshas_digit_hits每场排名因子](#排名因子)

  • 加入is_nounis_latinis_number,和has_digit每个术语标志; 加入相应的is_noun_wordsis_latin_wordsis_number_words,和has_digit_words每个查询的排名的因素; 并为UDF添加了查询因素支持(请参阅参考资料sphinxudf.h

  • 添加了在线查询流过滤 SET GLOBAL sql_fail_filter

  • 添加了在线查询流日志记录 SET GLOBAL sql_log_file

  • 加入SLICEAVGSLICEMAXSLICEMIN功能,以及STRPOS(str,conststr)功能

次要新增功能:

  • exceptions文件添加了哈希注释支持
  • 添加了--dummy <arg>切换到searchd(用于快速识别进程列表中的特定实例)
  • 向其中添加了IDF信息,术语标志和JSON格式输出CALL KEYWORDS(对于JSON输出,请使用CALL KEYWORDS(..., 1 AS json)
  • 添加IS NULLIS NOT NULL检查ALL()ANY()JSON迭代器
  • 已添加last_good_id到TSV索引错误报告中
  • ram_segments计数器添加到SHOW INDEX STATUS,并重命名了两个计数器(ram_chunkram_segments_bytesdisk_chunksdisk_segments
  • 添加sql_query_kbatch指令,不推荐使用的sql_query_killlist指令
  • 增加了<sphinx:kbatch>对XML源的支持
  • 记录了一些半隐藏的选项(net_spin_msec例如)

变更和改进:

  • 改进了对表达式中长常量列表的解析,thread_stack现在所需的时间大大减少了
  • 改进了stopwords处理方式,修复了哈希冲突问题
  • 改进的stopwords指令,使其成为多值
  • 改进的global_idf处理方式,使全局IDF完全独立于每个索引DF
  • 改进了EXPLAIN,确保始终报告真实的查询计划和统计信息
  • 改进的统计信息精度输出,用于1毫秒以下的查询时间,并且通常提高了内部查询计时精度
  • 改进了在表达式中检查参数类型的功能,并修复了一堆遗漏的情况(GEODIST()vs JSON 上的问题,COALESCE()args检查中的崩溃等)
  • 改善了FACET处理方式,现在必须始终进行单搜索优化
  • 更改indexer --nohup.new成功重命名索引文件
  • 更改query_time了分布式索引的度量标准行为,现在它将考虑墙壁时间
  • 删除了可能通过API获得的“搜索所有索引”语法残留
  • 移除了umask searchd.log

主要优化:

  • 优化的频繁的1部分和2部分ORDER BY子句,加速1.1 倍
  • 优化的全扫描查询,加速高达1.2倍以上
  • 针对矢量DOT()等几种情况进行了优化,int8加速高达2倍以上
  • 优化的方面,加速1.1倍

修正:

  • 修复了ORDER BY RAND()已损坏的问题WEIGHT()(也启用了它来对查询进行分组)
  • 修复字形中的哈希注释语法
  • 修复了一些单词形式的比赛
  • 修复了与以下相关的几个僵局 ATTACH
  • 修复了max_window_hits()exact_order因素的一些问题
  • 修复了插入重复值时罕见的B树崩溃的问题
  • 修复了一个罕见的TSV索引问题(由于非常罕见的缓冲区边界问题,格式正确的文件可能无法索引)
  • 修复了某些CPU和glibc组合上的分布式搜索偶尔崩溃的问题(双发行版)
  • SHOW META少索引后修正错误SELECT
  • 固定ALL()ANY()vs优化的JSON向量,以及固定的优化的int64 JSON向量访问器
  • 修复了SHOW THREADS ... OPTION columns=X永久限制线程描述的限制
  • 固定的/searchdHTTP端点错误格式
  • 固定的按索引查询统计信息与RT索引
  • 修复了查询解析器有时在高ASCII码上失败的问题
  • 修复了导致不正确或意外处理cutoff以及其他查询限制的一些问题
  • 修复了一些json_packed_keys问题
  • 固定了MVA64值剪辑 INSERT
  • 固定偶尔崩溃和/或内存损坏UPDATEINSERT
  • SNIPPET(field,QUERY())在某种程度上固定大小写(QUERY()在这种情况下,我们现在过滤掉查询语法并将其视为一堆单词)
  • 修复了在RT中在JSON上读取索引可能会错误地WHERE从查询中禁用其他条件的问题
  • 修复了许多与方面有关的问题(偶尔无法并行执行,偶尔崩溃等)
  • 通过SphinxAPI修复了空索引列表上的崩溃
  • XML / TSV / CSV源的固定架构属性顺序
  • 固定粘性regexp_filtervsATTACH

版本3.1.1,2018年10月17日#

  • 添加了indexer --dump-rows-tsv开关,并重命名--dump-rows--dump-rows-sql
  • 添加了COALESCE()对JSON的初始功能支持(请注意,它将计算浮点数!)
  • 在表达式中增加了对!=INNOT IN语法的支持
  • 添加prefix_tokenssuffix_tokens选项blend_mode指令
  • 添加了OPTION rank_fields,您可以指定用于与表达式或ML(UDF)排名进行排名的字段
  • 将明确的重复文档(docid)抑制添加回 indexer
  • 添加batch_size变量到SHOW META
  • 添加csvpipe_headertsvpipe_header指令
  • 已将sql_xxx计数器添加到SHOW STATUS,通常会清理计数器
  • 添加了混合代码索引,可通过blend_mixed_codesmixed_codes_fields指令使用
  • 添加OPTION inner_limit_per_index以显式控制嵌套分片选择中的重新排序
  • 添加了硬限制max_matches(必须小于100M)
  • 优化的Postgres索引CPU和RAM的使用非常显着
  • FACET具有表达式和简单的按属性(没有别名!)构面的优化查询;在这种情况下,现在可以进行多类别优化
  • 优化的id查询(UPDATE ... WHERE id=123现在查询会更快)
  • 优化的结果集聚合与嵌套分片选择
  • PACKEDFACTORS()大量优化存储(使用最多可提高60倍max_matches=50000
  • 改进了UDF错误处理,现在error参数是消息缓冲区,而不仅仅是1个字符的标志
  • 改进了嵌套分片选择的重新排序,现在减少了混乱(默认情况下,不再缩放内部LIMIT
  • 改进的searchd --listen开关,多个--listen现在实例是允许的,并且--console不再需要
  • 改进了失败的分配报告,并增加了巨大的分配跟踪
  • 除去传统@count@weight@expr@geodist语法的支持
  • 除去传统SetWeights()SetMatchMode()SetOverride()SetGeoAnchor()电话,SPH_MATCH_xxx常量和SPH_SORT_EXPR排序从API的模式
  • 删除了旧版spelldump实用程序
  • 删除未使用的.sha索引文件
  • 删除了无关的“没有额外的索引定义”警告

主要修复:

  • 修复了由某些复杂(通常很少见)的情况和/或设置组合导致的9+次崩溃
  • 修复了2个由于索引数据损坏而导致的崩溃(在箭头和字典中)
  • 修复了Windows上的纯索引锁定问题
  • 修复了处理字符串和NULL的JSON字段(不再有诸如NULL对象通过json.col = 0测试的极端情况)
  • 在某些条件下修复位置(短语/命令/句子等)运算符和修饰符中的匹配项丢失问题
  • 在某些情况下(很少见)修复了与哈希相关的挂断
  • 修复了使用JSON字段时表达式中的几种类型推断问题

其他修复:

  • 修复了min_best_span_pos有时关闭的问题
  • 修复了丢失global_idf文件的行为
  • 固定indextool --checkvs字符串属性以及vs空JSON
  • 修复了混合形式与多形式形式的行为(现在可预测的工作得多)
  • 固定查询解析器与仅通配符
  • 修复了MySQL 8.0+客户端无法连接的问题
  • 在启动时修复了偶尔的信号量竞赛
  • 固定OPTIMIZEUPDATE种族 UPDATE现在会因超时而失败
  • 固定indexer --merge --rotatevs kbatches
  • 修复偶发旋转相关的死锁
  • 修复了一些内存泄漏

版本3.0.3,2018年3月30日#

  • 增加的BITCOUNT()功能和按位非运算符,例如SELECT BITCOUNT(~3)
  • 使searchd配置部分完全可选
  • 改善了min_infix_len行为,现在强制要求最少2个字符
  • 改进了文档,添加了几节
  • 固定的二进制构建性能
  • 修复了多个崩溃(与文档存储,片段,json_packed_keysRT中的线程相关)
  • 固定的无docid的SQL源,暂时禁止使用这些源(仍需要docid)
  • 修复了某些情况下表达式中的int-vs-float精度问题
  • 固定uptime柜台SHOW STATUS
  • 固定查询缓存与 PACKEDFACTORS()

版本3.0.2,2018年2月25日#

  • 增加full_field_hit排名因素
  • 添加了bm15排名因子名称(旧bm25名称误导,将被删除)
  • 显着优化了RT插入片段(在某些基准测试中,相对于3.0.1高达2-6倍)
  • 优化的exact_field_hit排名因子,影响现在可以忽略不计(约2-4%)
  • 改善indexer输出,减少视觉噪音
  • 改进的searchd --safetrace选项,现在跳过addr2line以避免偶发冻结
  • 改进的indexerMySQL驱动程序查找,现在还检查libmariadb.so
  • 修复了searchd由属性索引引起的罕见偶发性崩溃
  • 修复indexer了丢失的SQL驱动程序导致的崩溃,并改进了错误报告
  • 修复了searchd使用docstore进行多索引搜索时发生的崩溃
  • 修复了表达式解析器在BM25F()权重图中的字段屏蔽属性失败的问题
  • 修复了ALTER字段屏蔽属性与index_field_lengths案例不符的问题
  • 在某些情况下修复了垃圾数据写入(看似无害,但无论如何)
  • 修复了罕见的偶尔searchd启动失败(与线程相关)

版本3.0.1,2017年12月18日#

  • 3.x分支的首次公开发行

自v.2.x起的更改#

在制品:统治它们的最大变化尚未到来。全新的完全RT索引格式仍在进行中,尚不可用。不要通过担心,ETL indexer不会在任何地方去。此外,尽管完全和真正的RT,但新格式实际上在批索引编制方面已经更快

自Sphinx v.2.x以来的最大变化是:

  • 添加了DocStore,文档存储
  • 现在可以将原始文档内容存储到索引中
  • 基于磁盘的存储,RAM占用空间应最小
  • 再见,必须查询另一个数据库来获取数据
  • 添加了新的属性存储格式
  • 任意更新支持(包括MVA和JSON)
  • 再见,突然的尺寸限制
  • 添加属性索引,并支持JSON
  • WHERE gid=123查询现在可以利用A索引
  • WHERE MATCH('hello') AND gid=123查询现在可以有效地相交FT索引和A索引
  • 再见,必须使用假关键字
  • 添加了压缩的JSON密钥
  • 在内部切换到rowid,并将所有docid强制设置为64位

已经存在但仍处于预测试阶段的另外两项重大更改是:

  • 添加了“零配置”模式(./sphinxdata文件夹)
  • 添加索引复制

其他较小的优点是:

  • 添加了对xmlpipe,snowball stemmers和re2的永远在线支持(正则表达式过滤器)
  • 添加了blend_mode=prefix_tokens,并启用了空blend_mode
  • 添加的kbatch_source指令,以从源docid自动生成k批次(除了显式查询)
  • 补充SHOW OPTIMIZE STATUS声明
  • 增加exact_field_hit排名因素
  • 加入123.45f在JSON值的语法,为FLOAT32矢量优化的支持,并FVEC()DOT()功能
  • 在文档存储中添加了预索引数据以加快速度SNIPPETS()(通过hl_fields指令)
  • 更改了字段权重,现在允许零和负权重
  • 词干发生了变化,现在排除了带有数字的关键字

一堆遗留的东西被删除:

  • 除去dictdocinfoinfix_fieldsprefix_fields指令
  • 除去attr_flush_periodhit_formathitless_wordsinplace_XXXmax_substring_lenmva_updates_poolphrase_boundary_XXXsql_joined_fieldsubtree_XXX指令
  • 删除了旧的id32和id64模式,mysqlSE插件和indexer --keep-attrs开关

最后但并非最不重要的一点是,可以使用的新配置指令是:

  • docstore_typedocstore_blockdocstore_compdocstore_cache_size(每指数)让你一般配置DocStore
  • stored_fieldsstored_only_fieldshl_fields(每指数)让你配置要放什么DocStore
  • kbatchkbatch_source(按索引)更新与旧k列表相关的指令
  • updates_pool (按索引)设置vrow文件的增长步骤
  • json_packed_keyscommon部分)启用JSON密钥压缩
  • binlog_flush_modesearchd部分)更改每个操作的刷新模式(0 =无,1 = fsync,2 = fwrite)

快速更新警告:

  • 如果您正在使用,sql_query_killlist那么现在必须显式指定kbatch并列出k批处理应应用于的所有索引:
sql_query_killlist = SELECT deleted_id FROM my_deletes_log
kbatch = main

# or perhaps:
# kbatch = shard1,shard2,shard3,shard4

版权#

本文档的版权(c)2017-2020,Andrew Aksyonoff。作者特此授予您以逐字形式重新分发它以及与之捆绑在一起的Sphinx各自副本的权利。保留所有其他权利。