【问题标题】:Couchbase uses wrong indexes with N1QL parameterized queriesCouchbase 在 N1QL 参数化查询中使用了错误的索引
【发布时间】:2017-06-02 21:16:53
【问题描述】:

我在理解 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 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 查询优化器的权利和合法行为是什么?这是否意味着查询计划不考虑参数的实际值?我是否手动将值放入查询字符串或存在任何其他方式来使用具有正确索引选择的 N1Ql 参数化查询?

编辑

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

【问题讨论】:

  • 你可以试试 Couchbase 4.5.1 看看这个问题是否已经修复。
  • 不,即使在 4.6 中也没有修复
  • 显然,没有什么可修复的:)。请参阅下面的答案。

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


【解决方案1】:

首先你需要知道 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 次查询的记录。

【讨论】:

  • 抱歉,这并没有解决我的问题。即使我将此标志设置为 false 创建查询,我仍然会遇到性能问题。或许您还有其他建议?
  • 尝试使用覆盖索引,它可以解决大部分性能问题。假设如果您想使用 5 个属性进行查询,请在创建索引时指定这些属性
  • 请确保查询 n Json 对象中变量的映射应该相同。否则它不起作用。
  • 我正在使用覆盖索引,甚至在 ARRAY_COUNT(myDatabase.Array) 函数上创建了索引。此外,当我手动将参数值传递给查询时,我看到查询分析器使用了正确的索引。
  • 当我将 N1QLParams.build().adhoc(false) 添加到查询构建器时,我看到了相同的查询。我认为当我将此参数添加到查询构建器时,Java 会自动将这些参数的值传递到 N1QL 查询 json 对象中的“语句”子句中。因为据我所知,负责查询优化过程的是内部 Couchbase 引擎而不是 Java,所以我假设查询生成器必须将值传递到查询字符串中。
【解决方案2】:

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

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

因此,请勿将索引的“位置”中提到的任何字段作为选择参数传递。相反,在您的选择中硬编码_class = "path/MyClass",就像您为create index 所做的那样。一切都应该没问题。

这是 couchbase 问题跟踪系统中关于该问题的票证。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多