连接查询解析器

Join 查询解析器允许用户运行查询,以规范文档之间的关系。

Solr 运行用户选择的子查询(v 参数),识别匹配文档在目标字段(from 参数)中具有的所有值,然后返回包含这些值的目标字段(to 参数)的文档。

实际上,这些语义与 SQL 引擎中的“内部查询”非常相似。例如,考虑以下 Solr 查询

/solr/techproducts/select?q={!join from=manu_id_s to=id}title:ipod

此查询为每个制造商返回一个文档,该制造商生产的产品标题中包含“ipod”,在语义上与以下 SQL 查询相同

SELECT *
FROM techproducts
WHERE id IN (
    SELECT manu_id_s
    FROM techproducts
    WHERE title='ipod'
  )

连接操作基于术语进行,因此 fromto 字段必须使用兼容的字段类型。例如:StrFieldIntPointField 之间的连接将不起作用。同样,StrField 和使用 LowerCaseFilterFactoryTextField 之间的连接仅适用于字符串字段中已经小写的值。

参数

此查询解析器采用以下参数

from

必需

默认值:无

包含在 to 字段中查找值的字段的名称。可以是单值或多值,但必须具有与 to 字段中表示的字段兼容的字段类型。

to

必需

默认值:无

值的字段名称将根据在 from 字段中找到的值进行检查。可以是单值或多值,但必须具有与 from 字段兼容的字段类型。

fromIndex

可选

默认值:请参见说明

运行“from”查询(v 参数)的索引的名称,以及收集“from”值的位置。必须位于与处理请求的核心相同的节点上。如果未定义此参数,则默认为处理核心的值。有关更多信息,请参见下文的 跨单分片集合连接跨集合连接

score

可选

默认值:请参见说明

指示 Solr 返回有关“from”查询分数的信息。此参数的值控制返回哪种类型的聚合信息。选项包括 avg(平均值)、max(最大值)、min(最小值)、total(总计)或 none(无)。

如果未指定 method 但指定了 score,则使用 dvWithScore 方法。如果指定了 method 且不是 dvWithScore,则忽略 score 值。有关更多详细信息,请参见下文的 method 参数文档。

method

可选

默认值:请参见说明

确定 Solr 应使用哪几种查询实现。选项仅限于:indexdvWithScoretopLevelDV

如果未指定,默认值为 index,除非存在 score 参数,它会将其覆盖为 dvWithScore。每个实现都有其自己的性能特征,建议用户尝试确定哪种实现最适合其用例。详细信息和性能启发式方法如下。

index

默认的 method,除非指定了 score 参数。它使用术语索引结构来处理请求。性能会随着“from”字段中的基数和发布(术语出现)的数量而变化。当“from”字段具有低基数、当“to”端返回大量文档或无法容忍偶发的提交后速度变慢时(这是 index 避免的其他方法的缺点),请考虑此方法。

dvWithScore

返回结果文档旁边的可选“得分”统计信息。它使用 docValues 结构(如果可用),但在必要时会回退到字段缓存。第一次访问字段缓存会降低提交后的初始请求速度,并占用 JVM 堆上的额外空间,因此在大多数情况下建议使用 docValues。性能会随着“from”字段中匹配的值的数量线性变化。如果需要得分信息,则必须使用此方法,并且还应在“from”查询匹配的文档较少时考虑此方法,无论返回的“to”端文档数量如何。

dvWithScore 和单值数字

dvWithScore 方法不支持单值数字字段。建议从 7.0 之前的版本迁移的用户在迁移期间将字段类型更改为字符串并重建索引。

topLevelDV

仅当 tofrom 字段具有 docValues 数据时才能使用,并且当前不支持数字字段。它使用顶级 docValues 数据结构来查找结果。随着 from 字段中匹配的值的数量增加,这些数据结构的性能优于其他方法。但是,它们构建成本高,并且需要在每次提交后延迟填充,这会导致在每次提交后使用它们的第一个查询时速度有时会明显变慢。如果您频繁提交并且您的用例可以容忍静态预热查询,请考虑在 solrconfig.xml 中添加一个查询,以便将此工作作为提交本身的一部分完成,而不是直接附加到用户请求中。当“from”查询匹配大量文档且“to”结果集大小适中时考虑此方法,但仅当可以容忍偶发的提交后速度变慢时才考虑。

跨单分片集合进行连接

您还可以指定 fromIndex 参数以与另一个核心或单分片集合中的字段进行连接。如果在 SolrCloud 模式下运行,则 fromIndex 参数中指定的集合必须具有一个分片和一个副本,该副本位于您要连接到的集合具有副本的所有 Solr 节点上。

我们考虑一个示例,您希望使用 Solr 联接查询按获得奥斯卡奖的导演筛选电影。具体来说,想象一下我们有两个具有以下字段的集合

movies: id、title、director_id、…​

movie_directors: id、name、has_oscar、…​

要使用对movie_directors集合的 Solr 联接按获得奥斯卡奖的导演筛选电影,您可以向movies集合发送以下筛选查询

fq={!join from=id fromIndex=movie_directors to=director_id}has_oscar:true

请注意,筛选的查询条件 (has_oscar:true) 基于使用fromIndex指定的集合中的字段。请记住,您无法使用联接查询从fromIndex集合返回字段,您只能使用字段筛选“to”集合(电影)中的结果。

接下来,我们了解如何在集群中部署这些集合。想象一下movies集合部署到一个四节点 SolrCloud 集群,并且有两个分片,复制因子为 2。具体来说,movies集合在以下四个节点上有副本

节点 1:movies_shard1_replica1

节点 2:movies_shard1_replica2

节点 3:movies_shard2_replica1

节点 4:movies_shard2_replica2

要在movies集合的 Solr 联接查询中使用movie_directors集合,它需要在四个节点的每个节点上有一个副本。换句话说,movie_directors必须有一个分片和四个复制因子

节点 1:movie_directors_shard1_replica1

节点 2:movie_directors_shard1_replica2

节点 3:movie_directors_shard1_replica3

节点 4:movie_directors_shard1_replica4

在查询时,JoinQParser将访问movie_directors集合的本地副本以执行联接。如果本地副本不可用或处于活动状态,则查询将失败。此时,应该清楚的是,由于您仅限于一个分片,并且数据必须复制到需要它的所有节点,因此此方法更适用于 from 集合和 to 集合之间存在一对多关系的小型数据集。此外,如果您向“to”集合添加副本,则还需要为“from”集合添加副本。

有关更多信息,Erick Erickson 撰写了一篇关于联接性能的博客文章,标题为 Solr 和联接

联接多个分片集合

还可以联接具有

  1. 相同分片数

  2. 两个集合的router.name相同

  3. router.fielduniqueKey应对应于tofrom参数中使用的字段(请参阅下面的详细信息和异常)

  4. 集合的分片并置(请参阅下面的示例)

为了简化,我们使用从“多”联接到“一”的示例。不过,在其他情况下也可以使用相同的方法。

“to”集合 “from”集合

节点 1

products_shard1_replica1

skus_shard1_replica2

节点 2

products_shard1_replica2

skus_shard1_replica1

节点 3

products_shard2_replica1

skus_shard2_replica1

节点 4

products_shard2_replica2

skus_shard2_replica2

注意 shardN 如何与其来自其他端集合的对应项相对应。使用 AffinityPlacementPlugin.withCollectionShards 对齐集合分片。

以下是集合路由支持的选项

router.name 字段约束

隐式

fromto 匹配 router.field

plaincompositeId

fromto 匹配 uniqueKeyrouter.field

compositeId(默认值)

可以通过 checkRouterField=false 禁用约束

注意:第三种情况假设索引器在此情况下分配 sku.id=<product.id>!<sku_id>compositeId 逻辑将产品的 SKU 放入与产品同名的分片中。此外,您可以关闭 checkRouterField=false 并通过 _route_ 伪字段手动放置每个文档。

例如,如果 skus 集合具有 router.field=product_id,我们可以通过 q={!join to=id from=product_id fromIndex=skus}color:red 查找红色 SKU 的产品。

跨集合联接

跨集合联接过滤器是联接解析器的一种方法,它将针对远程 Solr 集合执行查询,以获取一组联接键,这些键将用作针对本地 Solr 集合的过滤器查询。

跨集合联接查询将创建一个 CrossCollectionQuery 对象。CrossCollectionQuery 将首先查询远程 Solr 集合并获取联接键的流式表达式结果。当联接键流式传输到节点时,将建立本地索引中匹配文档的位集。这避免了在任何给定时间将整组联接键保留在内存中。此位集在成功执行后插入到过滤器缓存中,就像 Solr 过滤器缓存的正常行为一样。

如果本地索引是根据联接键字段分片的,则跨集合联接可以利用称为 哈希范围查询解析器 的辅助查询解析器。哈希范围查询解析器负责仅返回哈希到给定值范围的文档。这允许 CrossCollectionQuery 查询远程 Solr 集合并仅返回与本地 Solr 集合中的特定分片匹配的联接键。这样做的好处是确保随着分片数量的增加,网络流量不会增加,并允许更大程度的可扩展性。

交叉集合联接查询适用于字符串和点类型字段。用于联接键的字段必须是单值的,并且已启用 docValues。

建议按联接键对本地集合进行分片,因为这允许利用上述优化。

交叉集合联接查询通常不应作为 q 参数的一部分使用。它被设计为用于过滤查询 (fq 参数),以确保适当的缓存。

被查询的远程 Solr 集合应具有已启用 docValues 的联接键的单值字段。

远程 Solr 集合没有任何特定分片要求。

solrconfig.xml 中的联接查询解析器定义

交叉集合联接有一些可以在 solrconfig.xml 中指定的配置选项。

routerField

可选

默认值:无

如果使用 CompositeID 路由器按联接字段将文档路由到分片,则应在此处的配置中指定该字段名称。这将允许解析器优化生成的 HashRange 查询。

allowSolrUrls

可选

默认值:无

如果指定,此字符串数组指定可以传递给 solrUrl 查询参数的白名单 Solr URL。如果没有此配置,则无法使用 solrUrl 参数。此限制对于防止攻击者使用 Solr 探索网络是必需的。

  <queryParser name="join" class="org.apache.solr.search.JoinQParserPlugin">
    <str name="routerField">product_id_s</str>
    <arr name="allowSolrUrls">
      <str>http://othersolr.example.com:8983/solr</str>
    </arr>
  </queryParser>

交叉集合联接查询参数

fromIndex

必需

默认值:无

要查询以检索联接键值集合的外部 Solr 集合的名称。

zkHost

可选

默认值:无

用于连接到 ZooKeeper 的连接字符串。zkHostsolrUrl 都是可选参数,并且最多应指定其中一个。如果未指定 zkHostsolrUrl,则将使用本地 ZooKeeper 集群。

solrUrl

可选

默认值:无

要查询的外部 Solr 节点的 URL。必须与 solrconfig.xml 中的 allowSolrUrls 参数中列出的白名单 URL 完全匹配。如果 URL 不匹配,则此参数将被有效禁用。

from

必需

默认值:无

外部集合中的联接键字段名称。

to

可选

默认值:无

本地集合中的联接键字段名称。

v

可选

默认值:无

作为本地参数替换的查询。这是将在远程集合中匹配文档的查询字符串。

路由

可选

默认:false

如果为 true,跨集合联接查询将使用每个分片的哈希范围来确定要为该分片检索的联接键集。此参数可提高跨集合联接的性能,但它取决于通过 to 字段路由的本地集合。如果未指定此参数,跨集合联接查询将尝试自动确定正确的值。

ttl

可选

默认:3600

跨集合联接查询在缓存中被视为有效的时间长度,以秒为单位。跨集合联接查询不会意识到远程集合的更改,因此,如果远程集合已更新,则缓存的跨集合查询可能会产生不准确的结果。在 ttl 周期到期后,跨集合联接查询将针对远程集合重新执行联接。

其他参数

任何常规 Solr 查询参数也可以指定/作为本地参数传递。

跨集合查询示例

http://localhost:8983/solr/localCollection/query?fl=id&q={!join method="crossCollection" fromIndex="otherCollection" from="fromField" to="toField" v="*:*"}