SQL 查询语言

Solr SQL 模块通过将 SQL 与 Solr 的全文搜索功能无缝结合,将 SQL 查询功能引入 Solr。支持 MapReduce 样式和 JSON 分面 API 聚合,这意味着可以使用 SQL 查询来支持高查询量高基数用例。

模块

这通过 sql Solr 模块提供,该模块需要在使用前启用。

SQL 架构

SQL 接口允许将 SQL 查询发送到 Solr,并作为响应获取流式传输回的文档。在底层,Solr 的 SQL 接口使用 Apache Calcite SQL 引擎将 SQL 查询转换为作为 流表达式实现的物理查询计划。

有关 Solr 如何支持 Solr SQL 查询的更多信息,请参见下面的 配置 部分。

Solr 集合和数据库表

在标准的 SELECT 语句(如 SELECT <expressions> FROM <table>)中,表名对应于 Solr 集合名。表名不区分大小写。

SQL 查询中的列名直接映射到要查询的集合的 Solr 索引中的字段。这些标识符区分大小写。支持别名,并且可以在 ORDER BY 子句中引用它们。

SELECT * 语法表示所有字段,仅受支持于带有 LIMIT 子句的查询。score 字段仅可用于包含 LIMIT 子句的查询。

例如,我们可以索引 Solr 的示例文档,然后构建类似这样的 SQL 查询

SELECT name_exact as name, manu as mfr, price as retail FROM techproducts ORDER BY retail DESC

我们使用的 Solr 中的集合是“techproducts”,我们要求返回“name_exact”、“manu”和“price”字段,别名“retail”用于排序,以显示最昂贵的到最便宜的产品。

Solr SQL 语法

Solr 支持广泛的 SQL 语法。

SQL 解析器不区分大小写

Solr 用于翻译 SQL 语句的 SQL 解析器不区分大小写。但是,为了便于阅读,本页上的所有示例都使用大写关键字。

仅在使用 LIMIT 时才支持 SELECT *

通常,你应该明确地投影每个查询需要返回的字段,避免使用 select *,这通常被认为是一种不好的做法,并且当底层 Solr 架构发生变化时可能会导致意外结果。但是,Solr 确实支持对也包含 LIMIT 子句的查询使用 select *。所有存储的字段和 score 都将包含在结果中。如果没有 LIMIT 子句,则只能返回启用了 docValues 的字段。

转义保留字

如果在 SQL 查询中使用了保留字,则 SQL 解析器将返回错误。可以使用反引号转义保留字并将其包含在查询中。例如

select `from` from emails

SELECT 语句

Solr 支持有限和无限选择查询。除了 SQL 语句中的 LIMIT 子句之外,这两种类型的查询之间的语法是相同的。但是,它们有非常不同的执行计划和对数据存储方式的不同要求。以下部分探讨了这两种类型的查询。

带有 LIMIT 的基本 SELECT 语句

有限选择查询遵循以下基本语法

SELECT fieldA as fa, fieldB as fb, fieldC as fc FROM tableA WHERE fieldC = 'term1 term2' ORDER BY fa desc LIMIT 100

我们已经通过此示例介绍了许多语法选项,因此让我们了解一下下面可能的内容。

WHERE 子句和布尔谓词

WHERE 子句必须在谓词的一侧有一个字段。不支持两个常量 (5 < 10) 或两个字段 (fielda > fieldb)。也不支持子查询。

WHERE 子句允许将 Solr 的搜索语法注入到 SQL 查询中。在示例中

WHERE fieldC = 'term1 term2'

上面的谓词将在 fieldC 中执行短语“term1 term2”的全文搜索。

要执行非短语查询,只需在单引号内添加括号。例如

WHERE fieldC = '(term1 term2)'

上述谓词在 fieldC 中搜索 term1term2

Solr 范围查询语法可按如下方式使用

WHERE fieldC = '[0 TO 100]'

复杂布尔查询可按如下方式指定

WHERE ((fieldC = 'term1' AND fieldA = 'term2') OR (fieldB = 'term3'))

要指定 NOT 查询,请按如下方式使用 AND NOT 语法

WHERE (fieldA = 'term1') AND NOT (fieldB = 'term2')

受支持的 WHERE 运算符

SQL 查询界面支持并下推最常见的 SQL 运算符,具体如下

运算符 说明 示例 Solr 查询

=

等于

fielda = 10

fielda:10

<>

不等于

fielda <> 10

-fielda:10

>

大于

fielda > 10

fielda:{10 TO *]

>=

大于或等于

fielda >= 10

fielda:[10 TO *]

<

小于

fielda < 10

fielda:[* TO 10}

<=

小于或等于

fielda <= 10

fielda:[* TO 10]

IN

指定多个值(多个 OR 子句的简写)

fielda IN (10,20,30)

(fielda:10 OR fielda:20 OR fielda:30)

LIKE

字符串或文本字段中的通配符匹配

fielda LIKE 'day%'

{!complexphrase}fielda:"day*"

BETWEEN

范围匹配

fielda BETWEEN 2 AND 4

fielda: [2 TO 4]

IS NULL

匹配具有空值的列

fielda IS NULL

(*:* -field:*)

IS NOT NULL

匹配具有值的列

fielda IS NOT NULL

field:*

  • 对于不等于,请使用 <> 代替 !=

  • IN、LIKE、BETWEEN 支持 NOT 关键字,以查找条件不为真的行,例如 fielda NOT LIKE 'day%'

  • 字符串文本必须用单引号引起来;双引号表示数据库对象,而不是字符串文本。

  • 可以使用带有星号通配符的简单 LIKE,例如 field = 'sam*';这是 Solr 特有的,不属于 SQL 标准。

  • IN 子句的最大值数量受为你的集合配置的 maxBooleanClauses 限制。

  • 在对多值字段执行 AND 范围查询时,如果 AND 谓词似乎是分离的集合,Apache Calcite 会短路到零结果。例如,从单值字段的角度来看,b_is <= 2 AND b_is >= 5 似乎对 Calcite 来说是分离的集合。但是,对于多值字段来说可能并非如此,因为 Solr 可能会匹配文档。解决方法是在括号中使用等式表达式中直接使用 Solr 查询语法:b_is = '(+[5 TO *] +[* TO 2])'

ORDER BY 子句

ORDER BY 子句直接映射到 Solr 字段。支持多个 ORDER BY 字段和方向。

在指定了限制的查询中,score 字段在 ORDER BY 子句中被接受。

如果 ORDER BY 子句包含 GROUP BY 子句中的确切字段,则对返回的结果没有限制。如果 ORDER BY 子句包含与 GROUP BY 子句不同的字段,则自动应用 100 的限制。要增加此限制,您必须在 LIMIT 子句中指定一个值。

按字段排序区分大小写。

OFFSET 与 FETCH

指定 ORDER BY 子句的查询还可以使用 OFFSET(基于 0 的索引)和 FETCH 运算符来浏览结果;不支持没有 FETCHOFFSET,并且会生成异常。例如,以下查询请求 10 个结果的第二页

ORDER BY ... OFFSET 10 FETCH NEXT 10 ROWS ONLY

使用 SQL 分页与在使用 startrows 的 Solr 查询中分页遭受相同的性能损失,其中分布式查询必须从每个分片中过度提取 OFFSET + LIMIT 文档,然后对每个分片的搜索结果进行排序以生成返回给客户端的搜索结果页面。因此,此功能只应用于较小的 OFFSET / FETCH 大小,例如每个分片最多分页 10,000 个文档。Solr SQL 不会强制执行任何硬限制,但您深入搜索结果的程度越深,每个后续页面请求所花费的时间就越长,并且消耗的资源就越多。Solr 的 cursorMark 深度分页功能在 SQL 中不受支持;相反,使用没有 LIMIT 的 SQL 查询通过 /export 处理程序流式传输大型结果集。SQL OFFSET 不适用于深度分页类型的用例。

LIMIT 子句

将结果集限制为指定的大小。在上面的示例中,子句 LIMIT 100 将结果集限制为 100 条记录。

有限查询和无限查询之间有一些需要注意的区别

  • 有限查询支持字段列表和 ORDER BY 中的 score。无限查询不支持。

  • 有限查询允许字段列表中的任何存储字段。无限查询要求将字段存储为 DocValues 字段。

  • 有限查询允许 ORDER BY 列表中的任何索引字段。无限查询要求将字段存储为 DocValues 字段。

  • 如果某个字段已编入索引但未存储或没有 docValues,你可以对该字段进行筛选,但无法在结果中返回该字段。

SELECT DISTINCT 查询

SQL 接口支持 SELECT DISTINCT 查询的 MapReduce 和 Facet 实现。

MapReduce 实现将元组随机分配到执行 Distinct 操作的辅助节点。此实现可以在极高基数字段上执行 Distinct 操作。

Facet 实现使用 JSON Facet API 将 Distinct 操作下推到搜索引擎中。此实现专为低到中等基数字段上的高性能、高 QPS 场景而设计。

aggregationMode 参数在 JDBC 驱动程序和 HTTP 接口中均可用,用于选择底层实现(map_reducefacet)。SQL 语法对于这两种实现相同

SELECT distinct fieldA as fa, fieldB as fb FROM tableA ORDER BY fa desc, fb desc

统计函数

SQL 接口支持对数字字段计算的简单统计。支持的函数有 COUNT(*)COUNT(DISTINCT field)APPROX_COUNT_DISTINCT(field)MINMAXSUMAVG

由于这些函数永远不需要对数据进行随机分配,因此聚合会被下推到搜索引擎中,并由Stats 组件生成。

SELECT COUNT(*) as count, SUM(fieldB) as sum FROM tableA WHERE fieldC = 'Hello'

APPROX_COUNT_DISTINCT 指标使用 Solr 的 HyperLogLog (hll) 统计函数计算给定字段的近似基数,当查询性能很重要且不需要精确计数时应使用该指标。

GROUP BY 聚合

SQL 接口还支持 GROUP BY 聚合查询。

SELECT DISTINCT 查询一样,SQL 接口同时支持 MapReduce 实现和 Facet 实现。MapReduce 实现可以在极高基数字段上构建聚合。Facet 实现提供对中等基数字段的高性能聚合。

使用聚合的基本 GROUP BY

下面是一个请求聚合的 GROUP BY 查询的基本示例

  SELECT fieldA as fa, fieldB as fb, COUNT(*) as count, SUM(fieldC) as sum, AVG(fieldY) as avg
    FROM tableA
   WHERE fieldC = 'term1 term2'
GROUP BY fa, fb
  HAVING sum > 1000
ORDER BY sum asc
   LIMIT 100

我们来逐一分解

列标识符和别名

列标识符可以包含 Solr 索引中的字段和聚合函数。支持的聚合函数有

  • COUNT(*):对一组分区中的记录数进行计数。

  • SUM(field):对一组分区中的数字字段求和。

  • AVG(field):对一组分区中的数字字段求平均值。

  • MIN(field):对一组分区中的数字字段返回最小值。

  • MAX(field):对一组分区中的数字字段返回最大值。

字段列表中的非函数字段决定了计算聚合的字段。

Solr 目前不支持使用 COUNT(DISTINCT <field>) 计算每个组内特定字段的不同值数量;只能为每个 GROUP BY 维度计算 COUNT(*)

HAVING 子句

HAVING 子句可以包含字段列表中列出的任何函数。支持此类复杂的 HAVING 子句

  SELECT fieldA, fieldB, COUNT(*), SUM(fieldC), AVG(fieldY)
    FROM tableA
   WHERE fieldC = 'term1 term2'
GROUP BY fieldA, fieldB
  HAVING ((SUM(fieldC) > 1000) AND (AVG(fieldY) <= 10))
ORDER BY SUM(fieldC) ASC
   LIMIT 100

聚合模式

Solr 的 SQL 功能可以通过两种方式处理聚合(结果分组)

  • facet:这是默认聚合模式,它使用 JSON Facet API 或 StatsComponent 进行聚合。在此场景中,聚合逻辑被推送到搜索引擎中,并且只有聚合被发送到网络中。这是 Solr 的正常操作模式。当 GROUP BY 字段的基数较低到中等时,这种模式很快。但是,当 GROUP BY 字段中具有高基数字段时,它就会崩溃。

  • map_reduce:此实现将元组随机分配到工作节点,并在工作节点上执行聚合。它涉及对整个结果集进行排序和分区,然后将其发送到工作节点。在此方法中,元组按 GROUP BY 字段排序后到达工作节点。然后,工作节点可以一次对一个组进行汇总。这允许进行无限基数聚合,但是您需要付出将整个结果集通过网络发送到工作节点的代价。

向 Solr 发送请求时,这些模式使用 aggregationMode 属性定义。

聚合模式之间的选择取决于您正在处理的字段的基数。如果您对分组的字段具有低到中等基数,则“facet”聚合模式将为您提供更高的性能,因为只有最终组被返回,这与当前 facet 的工作方式非常相似。但是,如果您在字段中具有高基数,“map_reduce”聚合模式与工作节点一起提供了性能更高的选项。

配置

用于 SQL 接口的请求处理程序配置为隐式加载,这意味着开始使用此功能几乎不需要做什么。

/sql 请求处理程序

/sql 处理程序是并行 SQL 接口的前端。所有 SQL 查询都发送到 /sql 处理程序进行处理。该处理程序还在 map_reduce 模式下运行 GROUP BYSELECT DISTINCT 查询时协调分布式 MapReduce 作业。默认情况下,/sql 处理程序将从其自己的集合中选择工作节点来处理分布式操作。在此默认情况下,/sql 处理程序所在的集合充当 MapReduce 查询的默认工作者集合。

默认情况下,/sql 请求处理程序配置为隐式处理程序,这意味着它始终在每个 Solr 安装中启用,并且不需要进一步配置。

SQL 请求授权

如果您的 Solr 集群配置为使用 基于规则的授权插件,则需要对您打算针对其执行 SQL 查询的所有集合的 /sql/select/export 端点授予 GETPOST 权限。/select 端点用于 LIMIT 查询,而 /export 处理程序用于没有 LIMIT 的查询,因此在大多数情况下,您需要授予对两者都访问权限。如果您正在为 /sql 处理程序使用工作集合,则只需要为工作集合授予对 /sql 端点的访问权限,而无需授予对数据层中集合的访问权限。在后台,SQL 处理程序还使用内部 Solr 服务器标识向 /admin/luke 端点发送请求以获取集合的模式元数据。因此,您无需为用户授予对 /admin/luke 端点的明确权限来执行 SQL 查询。

如下文 最佳实践 部分所述,您可能希望为并行化 SQL 查询设置一个单独的集合。如果您有高基数字段和大量数据,请务必查看该部分并考虑使用单独的集合。

/stream 和 /export 请求处理程序

流式 API 是一个可扩展的并行计算框架,用于 SolrCloud。 流式表达式 为流式 API 提供了一个查询语言和一个序列化格式。

流式 API 提供对快速 MapReduce 的支持,使其能够对极大型数据集执行并行关系代数。在底层,SQL 接口使用 Apache Calcite SQL 解析器解析 SQL 查询。然后它将查询转换为并行查询计划。并行查询计划使用流式 API 和流式表达式表示。

/sql 请求处理程序一样,/stream/export 请求处理程序配置为隐式处理程序,并且不需要进一步配置。

字段

在某些情况下,SQL 查询中使用的字段必须配置为 DocValue 字段。如果查询是无限的,则所有字段都必须是 DocValue 字段。如果查询是有限的(使用 limit 子句),则字段不必启用 DocValues。

多值字段

项目列表中的多值字段将作为值的 List 返回;使用 JDBC 时,使用 getObject(col) 检索多值字段,然后转换为 List。通常,你可以投影、筛选和分组,但不能按多值字段排序。

发送查询

SQL 接口提供了基本的 JDBC 驱动程序和 HTTP 接口来执行查询。

JDBC 驱动程序

JDBC 驱动程序随 SolrJ 一起提供。以下是使用 JDBC 驱动程序创建连接并执行查询的示例代码

Connection con = null;
try {
    con = DriverManager.getConnection("jdbc:solr://" + zkHost + "?collection=collection1&aggregationMode=map_reduce&numWorkers=2");
    stmt = con.createStatement();
    rs = stmt.executeQuery("SELECT a_s, sum(a_f) as sum FROM collection1 GROUP BY a_s ORDER BY sum desc");

    while(rs.next()) {
        String a_s = rs.getString("a_s");
        double s = rs.getDouble("sum");
    }
} finally {
    rs.close();
    stmt.close();
    con.close();
}

连接 URL 必须包含 zkHostcollection 参数。集合必须是指定 ZooKeeper 主机上的有效 SolrCloud 集合。集合还必须使用 /sql 处理程序进行配置。aggregationModenumWorkers 参数是可选的。

HTTP 接口

Solr 通过 /sql 处理程序接受 SQL 查询。

以下是执行 facet 模式 SQL 聚合查询的示例 curl 命令

curl --data-urlencode 'stmt=SELECT to, count(*) FROM collection4 GROUP BY to ORDER BY count(*) desc LIMIT 10' http://localhost:8983/solr/collection4/sql?aggregationMode=facet

以下是示例结果集

{"result-set":{"docs":[
   {"count(*)":9158,"to":"[email protected]"},
   {"count(*)":6244,"to":"[email protected]"},
   {"count(*)":5874,"to":"[email protected]"},
   {"count(*)":5867,"to":"[email protected]"},
   {"count(*)":5595,"to":"[email protected]"},
   {"count(*)":4904,"to":"[email protected]"},
   {"count(*)":4622,"to":"[email protected]"},
   {"count(*)":3819,"to":"[email protected]"},
   {"count(*)":3678,"to":"[email protected]"},
   {"count(*)":3653,"to":"[email protected]"},
   {"EOF":"true","RESPONSE_TIME":10}]}
}

请注意,结果集是一个元组数组,其中包含与 SQL 列列表匹配的键/值对。最后一个元组包含 EOF 标志,表示流的结束。

最佳实践

单独的处理程序集合

/sql 处理程序单独创建 SolrCloud 处理程序集合是有意义的。可以使用 SolrCloud 的标准集合 API 创建此集合。

由于此集合仅存在于处理 /sql 请求和提供处理程序节点池,因此此集合不需要保存任何数据。处理程序节点是从 /sql 处理程序集合中所有可用节点池中随机选择的。因此,要动态增长此集合,可以将副本添加到现有分片。添加新副本后,它们将自动投入使用。

并行 SQL 查询

前面的部分描述了 SQL 接口如何将 SQL 语句转换为流表达式。请求的一个参数是 aggregationMode,它定义查询是否应使用类似 MapReduce 的混洗技术或将操作推送到搜索引擎中。

并行查询

并行 SQL 架构包含三个逻辑层:SQL 层、处理程序 层和数据表 层。默认情况下,SQL 层和处理程序层会折叠到同一个物理 SolrCloud 集合中。

SQL 层

SQL 层是 /sql 处理程序所在的位置。/sql 处理程序获取 SQL 查询并将其转换为并行查询计划。然后,它选择工作程序节点来执行该计划,并将查询计划发送到每个工作程序节点以并行运行。

工作程序节点执行查询计划后,/sql 处理程序随后执行工作程序节点返回的元组的最终合并。

工作程序层

工作程序层中的工作程序从 /sql 处理程序接收查询计划并执行并行查询计划。并行执行计划包括需要在数据表层上进行的查询以及满足查询所需的关系代数。分配给查询的每个工作程序节点从数据表中随机处理 1/N 的元组。工作程序节点执行查询计划并将元组流式传输回工作程序节点。

数据表层

数据表层是表所在的位置。每个表都是其自己的 SolrCloud 集合。数据表层从工作程序节点接收查询并发出元组(搜索结果)。数据表层还处理发送到工作程序的元组的初始排序和分区。这意味着元组在进入网络之前始终进行排序和分区。分区元组直接以正确的排序顺序发送到正确的工作程序节点,以便进行缩减。

image
图 1. 并行 SQL 查询如何分布

上图将三个层分解为不同的 SolrCloud 集合,以提高清晰度。实际上,/sql 处理程序和工作程序集合默认共享同一个集合。

该图像显示了单个并行 SQL 查询(MapReduce 上的 SQL)的网络流。当 map_reduce 聚合模式用于 GROUP BY 聚合或 SELECT DISTINCT 查询时,将使用此网络流。当使用 facet 聚合模式时,将使用传统的 SolrCloud 网络流(不含工作程序)。

以下是流程说明

  1. 客户端将 SQL 查询发送到 /sql 处理程序。该请求由单个 /sql 处理程序实例处理。

  2. /sql 处理程序解析 SQL 查询并创建并行查询计划。

  3. 查询计划被发送到工作程序节点(以绿色显示)。

  4. 工作程序节点并行执行该计划。该图表显示了每个工作程序节点联系数据表层中的一个集合(以蓝色显示)。

  5. 数据表层中的集合是 SQL 查询中的表。请注意,该集合有五个分片,每个分片有 3 个副本。

  6. 请注意,每个工作程序从每个分片中联系一个副本。由于有 5 个工作程序,因此每个工作程序从每个分片中返回 1/5 的搜索结果。分区在数据表层内部完成,因此在网络中不会出现数据重复。

  7. 另外,请注意,使用此设计,数据层中的所有副本都会同时对数据进行洗牌(排序和分区)。随着分片、副本和工作器数量的增加,此设计允许将大量的计算能力应用于单个查询。

  8. 工作器节点并行处理从数据表层返回的元组。工作器节点执行满足查询计划所需的关联代数。

  9. 工作器节点将元组流回 /sql 处理程序,在此处执行最终合并,最后将元组流回客户端。

SQL 客户端和数据库可视化工具

SQL 接口支持从 SQL 客户端和数据库可视化工具发送的查询。

本指南包含配置以下工具和客户端的文档

通用客户端

对于大多数基于 Java 的客户端,需要将以下 jar 放置在客户端类路径中

  • SolrJ 依赖项 .jar s 位于 $SOLR_TIP/server/solr-webapp/webapp/WEB-INF/lib/*$SOLR_TIP/server/lib/ext/* 中。在 Solr 发行版中,这些依赖项未与 Solr 的依赖项分开,因此您必须全部包含或手动选择所需的确切集合。请参阅 Maven 发行版,了解您版本所需的精确依赖项。

  • SolrJ .jar 位于 $SOLR_TIP/server/solr-webapp/webapp/WEB-INF/lib/solr-solrj-<version>.jar

如果您使用 Maven,则 org.apache.solr.solr-solrj 工件包含所需的 jar。

一旦 jar 在类路径中可用,Solr JDBC 驱动程序名称为 org.apache.solr.client.solrj.io.sql.DriverImpl,并且可以使用以下连接字符串格式进行连接

jdbc:solr://SOLR_ZK_CONNECTION_STRING?collection=COLLECTION_NAME

还可以将其他参数(如 aggregationModenumWorkers)可选地添加到连接字符串中。