搜索分析

查询的解释方式

考虑以下复杂查询示例:

"hello world" @title "example program"~5 @body python -(php|perl) @* code

该搜索的完整含义是:

  • 在文档的任何字段中定位相邻的单词 'hello' 和 'world';
  • 此外,同一文档的标题字段中必须包含单词 'example' 和 'program',两者之间最多有但不包括 5 个单词;(例如,“example PHP program” 会匹配,但“example script to introduce outside data into the correct context for your program” 不会匹配,因为两个词之间有 5 个或更多单词)
  • 此外,同一文档的正文字段中必须包含单词 'python',同时排除 'php' 或 'perl';
  • 最后,同一文档的任何字段中必须包含单词 'code'。

OR 运算符优先于 AND,因此“looking for cat | dog | mouse” 意味着“looking for (cat | dog | mouse)”,而不是“(looking for cat) | dog | mouse”。

为了理解查询将如何执行,Manticore Search 提供了查询分析工具,用于检查由查询表达式生成的查询树。

在 SQL 中分析查询树

要启用带有 SQL 语句的全文查询分析,必须在执行所需查询之前激活它:

SET profiling =1;
SELECT * FROM test WHERE MATCH('@title abc* @body hey');

要查看查询树,请在运行查询后立即执行 SHOW PLAN 命令:

SHOW PLAN;

该命令将返回已执行查询的结构。请记住,3 个语句 - SET profiling、查询和 SHOW - 必须在同一会话中执行。

在 HTTP JSON 中分析查询

使用 HTTP JSON 协议时,只需启用 "profile":true,即可在响应中获得全文查询树结构。

{
  "table":"test",
  "profile":true,
  "query":
  {
    "match_phrase": { "_all" : "had grown quite" }
  }
}

响应将包含一个 profile 对象,其中包含一个 query 成员。

query 属性保存转换后的全文查询树。每个节点包括:

  • type:节点类型,可以是 AND、OR、PHRASE、KEYWORD 等。
  • description:该节点的查询子树,以字符串形式表示(SHOW PLAN 格式)
  • children:任何子节点(如果存在)
  • max_field_pos:字段内的最大位置

关键词节点还将包括:

  • word:转换后的关键词。
  • querypos:该关键词在查询中的位置。
  • excluded:关键词是否被排除在查询之外。
  • expanded:关键词是否由前缀扩展添加。
  • field_start:关键词必须出现在字段开头。
  • field_end:关键词必须出现在字段结尾。
  • boost:关键词的 IDF 将乘以此值。
‹›
  • SQL
  • JSON
  • PHP
  • Python
  • Python-asyncio
  • javascript
  • Java
  • C#
  • Rust
  • TypeScript
  • Go
📋
SET profiling=1;
SELECT * FROM test WHERE MATCH('@title abc* @body hey');
SHOW PLAN \G
‹›
Response
*************************** 1\. row ***************************
Variable: transformed_tree
   Value: AND(
  OR(fields=(title), KEYWORD(abcx, querypos=1, expanded), KEYWORD(abcm, querypos=1, expanded)),
  AND(fields=(body), KEYWORD(hey, querypos=2)))
1 row in set (0.00 sec)

在某些情况下,由于扩展和其他转换,评估后的查询树可能与原始查询树有显著差异。

‹›
  • SQL
  • JSON
  • PHP
  • Python
  • Python-asyncio
  • javascript
  • Java
  • C#
  • Rust
  • TypeScript
  • Go
📋
SET profiling=1;
SELECT id FROM forum WHERE MATCH('@title way* @content hey') LIMIT 1;
SHOW PLAN;
‹›
Response
Query OK, 0 rows affected (0.00 sec)
+--------+
| id     |
+--------+
| 711651 |
+--------+
1 row in set (0.04 sec)
+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Variable         | Value                                                                                                                                                                                                                                                                                                                                                                                                                   |
+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| transformed_tree | AND(
  OR(
    OR(
      AND(fields=(title), KEYWORD(wayne, querypos=1, expanded)),
      OR(
        AND(fields=(title), KEYWORD(ways, querypos=1, expanded)),
        AND(fields=(title), KEYWORD(wayyy, querypos=1, expanded)))),
    AND(fields=(title), KEYWORD(way, querypos=1, expanded)),
    OR(fields=(title), KEYWORD(way*, querypos=1, expanded))),
  AND(fields=(content), KEYWORD(hey, querypos=2))) |
+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

不运行查询时的分析

SQL 语句 EXPLAIN QUERY 允许显示给定全文查询的执行树,而无需对表执行实际的搜索查询。

‹›
  • SQL
SQL
📋
EXPLAIN QUERY index_base '@title running @body dog'\G
‹›
Response
 EXPLAIN QUERY index_base '@title running @body dog'\G
*************************** 1\. row ***************************
Variable: transformed_tree
   Value: AND(
      OR(
            AND(fields=(title), KEYWORD(run, querypos=1, morphed)),
            AND(fields=(title), KEYWORD(running, querypos=1, morphed))))
  AND(fields=(body), KEYWORD(dog, querypos=2, morphed)))

EXPLAIN QUERY ... option format=dot 允许以分层格式显示提供的全文查询的执行树,适合使用现有工具进行可视化,例如 https://dreampuf.github.io/GraphvizOnline

EXPLAIN QUERY graphviz example

‹›
  • SQL
SQL
📋
EXPLAIN QUERY tbl 'i me' option format=dot\G
‹›
Response
EXPLAIN QUERY tbl 'i me' option format=dot\G
*************************** 1. row ***************************
Variable: transformed_tree
   Value: digraph "transformed_tree"
{
0 [shape=record,style=filled,bgcolor="lightgrey" label="AND"]
0 -> 1
1 [shape=record,style=filled,bgcolor="lightgrey" label="AND"]
1 -> 2
2 [shape=record label="i | { querypos=1 }"]
0 -> 3
3 [shape=record,style=filled,bgcolor="lightgrey" label="AND"]
3 -> 4
4 [shape=record label="me | { querypos=2 }"]
}

查看匹配因子值

使用表达式排序器时,可以通过 PACKEDFACTORS() 函数显示计算出的因子值。

该函数返回:

  • 文档级别因素的值(例如 bm25、field_mask、doc_word_count)
  • 生成命中的每个字段的列表(包括 lcs、hit_count、word_count、sum_idf、min_hit_pos 等)
  • 查询中每个关键词及其 tf 和 idf 值的列表

这些值可用于理解为什么某些文档在搜索中获得较低或较高的分数,或用于优化现有的排名表达式。

‹›
  • SQL
SQL
📋
SELECT id, PACKEDFACTORS() FROM test1 WHERE MATCH('test one') OPTION ranker=expr('1')\G
‹›
Response
             id: 1
packedfactors(): bm25=569, bm25a=0.617197, field_mask=2, doc_word_count=2,
    field1=(lcs=1, hit_count=2, word_count=2, tf_idf=0.152356,
        min_idf=-0.062982, max_idf=0.215338, sum_idf=0.152356, min_hit_pos=4,
        min_best_span_pos=4, exact_hit=0, max_window_hits=1, min_gaps=2,
        exact_order=1, lccs=1, wlccs=0.215338, atc=-0.003974),
    word0=(tf=1, idf=-0.062982),
    word1=(tf=1, idf=0.215338)
1 row in set (0.00 sec)
Last modified: August 28, 2025