Couchbase使用错误的索引和N1QL参数化查询

时间:2017-01-18 13:11:15

标签: java spring-data couchbase spring-data-couchbase couchbase-java-api

我对了解couchbase查询计划的工作方式有疑问。 我使用SpringData和Couchbase 4.1,我提供了Couchbase Repository的自定义实现。在我的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 Data生成以下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查询优化器的正确和合法行为?这是否意味着查询计划没有考虑参数的实际值?我是否手动将值放入查询字符串或存在eny其他方式使用N1Ql参数化查询和正确的索引选择?

修改

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

2 个答案:

答案 0 :(得分:2)

首先,您需要了解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个查询记录。

答案 1 :(得分:1)

您的案例中的问题是由于您的索引包含'where'子句WHERE ( _class = "path/MyClass"),同时您在查询中将_class作为参数传递。

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

因此,不要将索引'where'中提到的任何字段作为选择参数传递。相反,请按照与_class = "path/MyClass"相同的方式在您的选择中对create index进行硬编码。一切都应该没问题。

这是沙发基地问题跟踪系统中关于该问题的票据。

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