更新时间:2024-10-30 GMT+08:00

如何使用Lucene搜索索引

GeminiDB Cassandra支持Lucene搜索索引已实现多维查询、文本检索、统计分析等能力,在使用体验上和原生二级索引相似,但同时拥有了更为丰富的语法支持。

当前Cassandra二级索引的痛点

原生Cassandra中二级索引的实现其实是创建了一张隐式的表,该表的Primary Key是创建索引的列,值为对应的Primary Key,实现相对简单,因此不可避免地带来了一些约束条件:

  • 第一主键只能用“=”查询。
  • 第二主键可以使用“=、>、<、>=、<=”。
  • 索引列只支持“=”查询。
  • 删除、更新太过频繁的列不适合建立索引。
  • High-cardinality列不适合做索引。

基于以上约束,Cassandra二级索引能提供的查询功能较为局限。

Lucene搜索索引架构

关键技术点:

内嵌Lucene搜索引擎,与存储引擎搭配,实现宽表存储引擎与搜索引擎的深度融合;

SQL层统一融合,在兼容原生Cassandra语法基础上,提供多维查询、文本检索、模糊查询、统计分析等能力,全面提升用户在海量数据场景下的查询体验。

图1 Lucene搜索索引架构

Lucene搜索索引使用方式举例

图2 Lucene搜索索引使用方式

表结构示例:

CREATE TABLE example (pk1 text, pk2 bigint, ck1 int,ck2 text,col1 int, col2 int, col3 text, col4 text, PRIMARY KEY ((pk1,pk2),ck1, ck2));

四个属性列创建Lucene搜索索引:

CREATE CUSTOM INDEX index_lucene ON test.example(col1,col2,col3,col4) USING LuceneGlobalIndex' 
WITH OPTIONS = {
'table_tokens': '3', 
'analyzed_columns': 'col4', 
'disable_doc_value': 'col4', 
'ordered_columns': 'col3,col4', 
'ordered_sequences': 'desc,asc', 
'analyzer_class': 'StandardAnalyzer'
};
表1 可选参数OPTIONS说明

参数名

作用

table_tokens

指定初始化Lucene搜索索引分片数,不指定默认为3,分片会占用一定的cpu和内存资源,并随数据量增长而增加。

analyzed_columns

指定用于全文搜索的列。

analyzer_class

指定全文搜索使用的分词器。

中文解析器:

'analyzer_class': 'SmartChineseAnalyzer'

标准解析器:

'analyzer_class': 'StandardAnalyzer'

IK解析器:

'analyzer_class': 'IKAnalyzer'

ordered_columns

指定Lucene搜索索引默认排序,不指定时默认与cassandra排序保持一致,多个索引列通过逗号隔开,需要注意:只有查询时排序方式与默认排序一致时,查询效率最高。

ordered_sequences

指定排序索引列升降序,asc代表升序,desc代表降序,需要与ordered_columns一一对应。

disable_doc_value

指定索引列不进行DocValues存储,对于不需要进行排序、聚合等操作的索引列可以禁用DocValues存储。

多维查询:任意索引列组合的嵌套查询,支持精确查询和范围查询。

SELECT * from example WHERE pk1>='a' and pk2>=1000 and ck2 in ('a','b','c') and col1 <= 4 and col2 >= 2;

count计数:获取数据表的总行数,或根据索引列具体查询条件返回命中的数据行数。

SELECT count(*) FROM example WHERE col1 > 3 AND EXPR(index_lucene, 'count');

索引列排序支持指定多个索引列排序规则,结合多维查询,返回指定排序的结果集。(通过JSON扩展语义支持,见下一节扩展JSON语义

模糊查询:支持前缀查询和通配符查询。

SELECT * FROM example WHERE col3 LIKE 'test%'; 
SELECT * FROM example WHERE col3 LIKE 'start*end';

聚合分析:按照索引列组合条件进行简单的聚合分析(sum/max/min/avg)

SELECT sum(col1) from example WHERE pk1>='a' and pk2>=1000 and col1 <= 4 and col2 >= 2;

全文检索:支持指定中/英文分词器,进行分词检索,返回相关性高的结果。

SELECT * FROM example WHERE col4 LIKE '%+test -index%';

扩展JSON语义:

表2 扩展JSON语义

关键字

作用

filter

在查询语句中json查询的关键字。

term

查询时判断某个document是否包含某个具体的值。

match

将被询值进行分词,进行全文检索。

range

查询指定某个字段在某个特定的范围。(范围查询子关键字:"eq"/"gte"/"gt"/"lte"/"lt")

bool

必须和 "must"、"should"、"must not" 一起组合出复杂的查询。

must

bool类型的子查询,封装"term"、"match"、"range" 查询。

should

bool类型的子查询,封装"term"、"match"、"range" 查询。

must not

bool类型的子查询,封装"term"、"match"、"range" 查询。

sort

支持全局索引列排序功能。

典型JSON查询语句示例:

{
  "filter": {
    "bool": {
      "should": [
        {"term": {"col1": 1, "col1": 2, "col1": 3, "col3": "testcase7"}}
      ], 
      "must": [
        {"range": {"col2": {"lte": 7, "gt": 0}, "ck1": {"gte": 2}}},
        {"match": {"col4": "+lucene -index"}}
      ]
    }
  }, 
  "sort": [{"col1":"desc"}, {"col2":"asc"}]
}

完整cql如下:

SELECT * from example where expr(index_lucene, '{"filter": {"bool": {"should": [{"term": {"col1": 1, "col1": 2, "col1": 3, "col3": "testcase7"}}], "must": [{"range": {"col2": {"lte": 7, "gt": 0}, "ck1": {"gte": 2}}},{"match": {"col4": "+lucene -index"}}]}}, "sort": [{"col1":"desc"}, {"col2":"asc"}]}');

下面对典型的查询场景cql语句结合JSON一起进行对比举例:

1. 带分区键的查询(指定pk1、pk2),需要将pk1和pk2从json条件中剥离出来,否则会影响性能。

SELECT * from example where pk1=*** and pk2=*** and expr(index_lucene, 'json');

2. 查询条件: col1=1。

SELECT * from example WHERE col1=1;
SELECT * from example WHERE expr(index_lucene, '{"filter": {"term": {"col1": 1}}}');
SELECT * from example WHERE expr(index_lucene, '{"filter": {"bool": {"must": [{"term": {"col1": 1}}]}}}');

上面三条语句,是等效的;类似这种情况,建议使用第一种的普通cql查询,只有当普通cql无法支持时,再使用json扩展查询;上面三个语句推荐顺序次为从上到下。

3. 查询条件:col1=1 and col2>=2。

SELECT * from example WHERE col1=1 and col2>=2;
SELECT * from example WHERE expr(index_lucene, '{"filter": {"term": {"col1": 1},"range": {"col2": {"gte": 2}}}}');
SELECT * from example WHERE expr(index_lucene, '{"filter": {"bool": {"must": [{"term": {"col1": 1}}, {"range": {"col2": {"gte": 2}}}]}}}');

与第一种相同,推荐普通cql查询。

4. 查询条件:col1=1 and (col2<2 or col2>3)。

SELECT * from example WHERE expr(index_lucene, '{"filter": {"bool": {"must": [{"term": {"col1": 1}}], "should": [{"range": {"col2": {"lt": 2}, "col2": {"gt": 3}}}]}}}');
SELECT * from example WHERE expr(index_lucene, '{"filter": {"bool": {"must": [{"term": {"col1": 1}}], "must_not": [{"range": {"col2": {"gte": 2, "lte": 3}}}]}}}');

上面两种方式效果相同,但是不推荐使用"must_not",性能不如"should"。

5. 查询条件:col1 in (1,2,3,4) and (col2<2 or col2>3)。

SELECT * from example WHERE expr(index_lucene, '{"filter": {"bool": {"should": [{"term": {"col1": 1, "col1": 2, "col1": 3, "col1": 4}}], "should": [{"range": {"col2": {"lt": 2}, "col2": {"gt": 3}}}]}}}');
SELECT * from example WHERE expr(index_lucene, '{"filter": {"bool": {"should": [{"term": {"col1": 1, "col1": 2, "col1": 3, "col1": 4}}], "must_not": [{"range": {"col2": {"gte": 2, "lte": 3}}}]}}}');

与4一样,上面两种方式效果相同,但是不推荐使用"must_not",性能不如"should"。

6. 带分区键single查询:pk1='a' and pk2=1000 and col1 in (1,2,3,4) and (col2<2 or col2>3)。

SELECT * from example WHERE pk1='a' and pk2=1000 and expr(index_lucene, '{"filter": {"bool": {"should": [{"term": {"col1": 1, "col1": 2, "col1": 3, "col1": 4}}], "should": [{"range": {"col2": {"lt": 2}, "col2": {"gt": 3}}}]}}}');

7. 查询条件:(((ck1<2 or ck1>=4) and (col1<2 or col1 >3)) or (pk1 in ('a', 'b', 'c'))) or (5<=col2<15 and pk2 > 2000)。

SELECT * from example WHERE expr(index_lucene, '{"filter": {"bool": {"should": [{"bool": {"should": [{"bool": {"must": [{"bool": {"should": [{"range": {"ck1": {"lt": 2}, "ck1": {"gte": 4}}}]}}, {"bool": {"should": [{"range": {"col1": {"lt": 2}, "col1": {"gt": 3}}}]}}]}}, {"bool": {"should": [{"term": {"pk1": "a", "pk1": "b", "pk1": "c"}}]}}]}}, {"bool": {"must": [{"range": {"col2": {"gte":5, "lte": 15}, "pk2": {"gt": 2000}}}]}}]}}}');

8. count 查询,也可使用json构造查询条件,上面的查询条件,进行count查询,语句如下

SELECT count(*) from example WHERE expr(index_lucene, '{"filter": {"bool": {"should": [{"bool": {"should": [{"bool": {"must": [{"bool": {"should": [{"range": {"ck1": {"lt": 2}, "ck1": {"gte": 4}}}]}}, {"bool": {"should": [{"range": {"col1": {"lt": 2}, "col1": {"gt": 3}}}]}}]}}, {"bool": {"should": [{"term": {"pk1": "a", "pk1": "b", "pk1": "c"}}]}}]}}, {"bool": {"must": [{"range": {"col2": {"gte":5, "lte": 15}, "pk2": {"gt": 2000}}}]}}]}}}');

注意事项:

  • 普通cql可以满足的查询条件,尽量避免依赖json查询。
  • 单分区查询,要将分区键条件单独作为查询条件,不要放入json中,否则会影响single查询的性能。
  • 尽量避免使用"must_not"。
  • 如果查询总是需要按照某些索引列排序输出,可以考虑在创建索引时指定该排序方式为默认排序以提升性能。