Couchbase 在 N1QL 参数化查询中使用了错误的索引

Couchbase uses wrong indexes with N1QL parameterized queries

我无法理解 couchbase 查询计划的工作方式。 我将 SpringData 与 Couchbase 4.1 一起使用,并且我提供了 Couchbase 存储库的自定义实现。在我自定义的 Couchbase 存储库实现中,我有以下方法:

String queryAsString = "SELECT MyDatabase.*, META().id as _ID, META().cas as _CAS FROM MyDatabase WHERE segmentId = $id AND _class = $class ORDER BY executionTime DESC LIMIT 1";
JsonObject params = JsonObject.create()
        .put(CLASS_VARIABLE, MyClass.class.getCanonicalName())
        .put(ID_VARIABLE, segmentId);

N1qlQuery query = N1qlQuery.parameterized(queryAsString, params);
List<MyClass> resultList = couchbaseTemplate.findByN1QL(query, SegmentMembers.class);
return resultList.isEmpty() ? null : resultList.get(0);

在结果中,Spring 数据生成以下 json 对象表示对 Couchbase 的查询:

{
    "$class":"path/MyClass",
    "statement":"SELECT MyDatabase.*, META().id as _ID, META().cas as _CAS from  MyDatabase where segmentId = $id AND _class = $class ORDER BY executionTime DESC LIMIT 1",
    "id":"6592c16a-c8ae-4a74-bc17-7e18bf73b3f8"
}

当我通过 Java 和 N1QL Rest Api 或通过 cbq consol 执行它时,问题出在性能上。为了在 cbq 中执行此查询,我只需将参数引用替换为精确值。

在 select 语句之前添加 EXPLAIN 子句后,我提到了不同的执行计划。通过 Java Spring Data 或 N1QL Rest Api 作为参数化查询执行此查询 我已经提到查询不使用我为这种情况创建的索引。索引定义如下:

CREATE INDEX `testMembers` ON MyDatabase `m`(`_class`,`segmentId`,`executionTime`) WHERE (`_class` = "path/MyClass") USING GSI;

所以,当我通过 cbq consol 执行查询时,Couchbase 使用我的 idnex,查询性能非常好。但是,当我通过 N1QL rest api 或 Java 执行此查询时,我发现该查询未使用我的索引。您可以在下面找到证明这一事实的部分执行计划:

"~children": [
{
  "#operator": "PrimaryScan",
  "index": "#primary",
  "keyspace": "CSM",
  "namespace": "default",
  "using": "gsi"
},

那么,问题是couchbase查询优化器的正确合法行为?这是否意味着查询计划不考虑参数的实际值?我是否手动将值放入查询字符串或存在任何其他方式来使用具有正确索引的 N1Ql 参数化查询 selection?

编辑

根据 shashi raj 的回答,我将 N1qlParams.build().adhoc(false) 参数添加到参数化 N1QL 查询中。这并不能解决我的问题,因为这个查询仍然存在性能问题。此外,当我打印查询时,我发现它与我之前描述的相同。所以,我的查询仍然错误分析并导致性能下降。

首先你需要知道 N1QL 参数化查询是如何工作的查询应该被传递为:

String query=  select * from bucketName where _class=$_class and segmentId=$segmentId LIMIT $limit ;

现在查询应该传递为:

N1QL.parameterized(query,jsonObject,N1qlParams.build().adhoc(false));

其中 jsonObject 将包含所有占位符值。

JsonObject jsonObject=JsonObject.create().put("_class","com.entity,user").put("segmentId","12345").put("limit",100);

N1qlParams.build().adhoc(false) 是可选的,因为如果您希望优化查询,它将使用它。它利用 LRU 来跟踪先前输入的查询并保留它的记录,以便下次它不需要解析查询并从我们称之为准备好的语句中获取它。

唯一的问题是 couchbase 只保留最后 5000 个查询的记录。

你的问题是因为你有一个带有 'where' 子句 WHERE ( _class = "path/MyClass") 的索引,同时你将 _class 作为参数传递在您的查询中。

因此,分析参数化查询的查询优化器不知道此查询可能使用为 _class = "path/MyClass" 创建的索引,因为它在 select 的位置 _class = $class。很简单吧?

因此,请勿将索引的 'where' 中提到的任何字段作为 select 参数传递。相反,在 select 中硬编码 _class = "path/MyClass",就像对 create index 所做的一样。一切都会好起来的。

这是 couchbase 问题跟踪系统中关于该问题的工单。

https://issues.couchbase.com/browse/MB-22185?jql=text%20~%20%22parameters%20does%20not%20use%20index%22