【问题标题】:Cassandra slice query quite weird resultsCassandra切片查询结果很奇怪
【发布时间】:2022-03-10 22:35:37
【问题描述】:

阅读DataStax blog,我正在测试切片查询,即使博客已经使用 Cassandra 2 完成并且我们在 3。

所以我定义了一个测试表如下。

CREATE TABLE foo(
  part_key TEXT,
  start_ts INT,
  end_ts INT,
  PRIMARY KEY(part_key, start_ts, end_ts)
) WITH CLUSTERING ORDER BY (start_ts ASC, end_ts ASC);

此表中插入了一些灯具。

INSERT INTO foo(part_key, start_ts, end_ts) VALUES ('A', 1, 5);
INSERT INTO foo(part_key, start_ts, end_ts) VALUES ('A', 2, 3);
INSERT INTO foo(part_key, start_ts, end_ts) VALUES ('B', 4, 7);
INSERT INTO foo(part_key, start_ts, end_ts) VALUES ('B', 9, 13);

INSERT INTO foo(part_key, start_ts, end_ts) VALUES ('B', 1, 2);

INSERT INTO foo(part_key, start_ts, end_ts)
VALUES ('B', 9, 9999); -- 9999 = symbolic value for no end

我先检查B分区:

> SELECT * FROM foo WHERE part_key='B';

 part_key | start_ts | end_ts
----------+----------+--------
        B |        1 |      2
        B |        4 |      7
        B |        9 |     13
        B |        9 |   9999

(4 rows)

我们无法执行“自然”查询SELECT * FROM foo WHERE part_key='B' AND start_ts <= 7 AND end_ts >= 7,因为它会引发以下错误。

InvalidRequest: Error from server: code=2200 [Invalid query] message="Clustering column "end_ts" cannot be restricted (preceding column "start_ts" is restricted by a non-EQ relation)"

在 DataStax 博客中有如下查询。

SELECT * FROM numberOfRequests
    WHERE cluster = ‘cluster1’
    AND date = ‘2015-06-05’
    AND datacenter = 'US_WEST_COAST'
    AND (hour, minute) >= (12, 30) AND (hour) < (14)

因此,我们使用以下切片查询尝试此解决方法,始终使用 start_ts &gt;= -9999,并允许一起指定 end_ts 的不等式条件。

SELECT * FROM foo WHERE part_key='B'
AND (start_ts, end_ts) >= (-9999, 7) AND start_ts <= 7; -- -9999 = min_value

它在没有任何警告的情况下执行,并给出如下结果。

 part_key | start_ts | end_ts
----------+----------+--------
        B |        1 |      2
        B |        4 |      7

(2 rows)

此时,第 1 行(带有start_ts = 1)对我来说不应该在那里,因为end_ts &gt;= 7 不适用于这一行。由于start_ts &gt;= -9999 始终为真,似乎第一个条件(start_ts, end_ts) &gt;= (-9999, 7) 被忽略了:结果与查询SELECT * FROM foo WHERE part_key='B' AND start_ts &lt;= 7 相同。

阅读博客示例,我在想(至少在 C2.2 中),(start_ts, end_ts) &gt;= (-9999, 7) AND start_ts &lt;= 7 表示 start_ts &gt;= -9999 AND end_ts &gt;= 7 AND start_ts &lt;= 7,类似于 (start_ts, end_ts) = (4, 7),表示 start_ts = 4 AND end_ts = 7,如下所示。

SELECT * FROM foo WHERE part_key='B' AND (start_ts, end_ts) = (4, 7);

 part_key | start_ts | end_ts
----------+----------+--------
        B |        4 |      7

(1 rows)

如何真正解释这种类似元组的不等式? 有什么办法可以更新它,让它“工作”?

【问题讨论】:

标签: cassandra cassandra-3.0


【解决方案1】:

您在这里遇到的是元组比较 - 因为您已将两列组合成一个元组,所以您得到的比较行为与您预期的略有不同。没错,只是不是你想的那样。

(start_ts, end_ts) >= (-9999, 7)

这并不意味着 start_ts &gt;= -9999 &amp;&amp; end_ts &gt;= 7 ,它意味着左(包括)边界是元组 (-9999,7) 。可能有一个元组 (-9998,1) 元组大于 (-9999,7) ,即使 end_ts==1 小于 7

【讨论】:

    【解决方案2】:

    让我们再插入一些记录

    INSERT INTO foo (part_key, start_ts , end_ts ) VALUES ( 'B', 1, 7);
    INSERT INTO foo (part_key, start_ts , end_ts ) VALUES ( 'B', 4, 8);
    INSERT INTO foo (part_key, start_ts , end_ts ) VALUES ( 'B', 9, 7);
    

    现在我们有了 part_key = 'B' 的数据

    cqlsh:test> SELECT * FROM foo WHERE part_key='B' ;
    
     part_key | start_ts | end_ts
    ----------+----------+--------
            B |        1 |      2
            B |        1 |      7
            B |        4 |      7
            B |        4 |      8
            B |        9 |      7
            B |        9 |     13
            B |        9 |   9999
    

    现在让我们查询这些数据:

    cqlsh:test> SELECT * FROM foo WHERE part_key='B' AND (start_ts, end_ts) >= (1, 4) AND (start_ts, end_ts) <= (9, 7);
    
     part_key | start_ts | end_ts
    ----------+----------+--------
            B |        1 |      7
            B |        4 |      7
            B |        4 |      8
            B |        9 |      7
    

    似乎结果不正确。但事实并非如此。我们的理解是错误的。

    Cassandra 将存储按复合字段(start_ts、end_ts)排序的数据,首先按 start_ts 排序,然后为每个 start_ts 排序 end_ts。当我们使用(start_ts, end_ts) &gt;= (1, 4) AND (start_ts, end_ts) &lt;= (9, 7) 查询时。 Cassandra 将 (start_ts, end_ts) 视为单个复合字段,其值介于 (1, 4) 到 (9,7) 之间

     part_key | start_ts | end_ts
    ----------+----------+--------
            B |        1 |      2
    -------------------------------> start range
            B |        1 |      7
            B |        4 |      7
            B |        4 |      8
            B |        9 |      7
    -------------------------------> end range       
            B |        9 |     13
            B |        9 |   9999
    

    希望你能理解。

    【讨论】:

    • 谢谢,它确认 Cassandra 无法按日期/范围查询
    • 是的,你必须从客户端过滤它
    • 只能说 Cassandra 只能找到匹配的数据 :-)
    • 没有关于此行为的任何信息表明 cassandra 无法按日期/范围查询。您正在使用元组 WHERE 子句。
    【解决方案3】:

    https://docs.datastax.com/en/dse/5.1/cql/cql/cql_using/whereClustering.html 中有一张很好的图片,它很好地解释了 Cassandra 如何将组合的聚类列分层存储在排序树状结构中。

    在你的情况下是:

    { "start_ts": 1 {
                 "end_ts": 2 },
      "start_ts": 4 {
                 "end_ts": 7 },
      "start_ts": 9 {
                 "end_ts": 13,
                 "end_ts": 9999 } }
    

    现在如果你这样做:

    SELECT * FROM foo WHERE part_key='B' 
    AND (start_ts, end_ts) >= (-9999, 7) AND start_ts <= 7;
    

    Cassandra 将在其树中搜索满足条件=&gt; (-9999, 7) 的第一个“节点”,即(从排序树的角度来看)(1,2)。 现在 Cassandra 将向上遍历树,返回属于找到的节点的每一行。 上限由start_ts &lt;= 7 给出。因此,Cassandra 将在遇到节点 (9,13) 时停止返回行。

    但是,仍然可以通过允许 Cassandra 允许过滤来执行您的初始查询。这是因为 Cassandra 遍历其树,现在需要根据条件过滤节点。实际查询是

    SELECT * FROM foo WHERE part_key='B' AND start_ts <= 7 AND end_ts >= 7 ALLOW FILTERING;
    

    注意:Cassandra 不鼓励使用ALLOW FILTERING,因为性能不可预测。假设您会要求它运行数百万行以仅过滤掉 3。在许多情况下,有一些方法可以优化数据结构以有效地从 Cassandra 中读取,通常使用中间查找表。但是,我已经体验到,在某些情况下,在组合聚类列上使用允许过滤的权衡可以接受到可以忽略不计,但这在很大程度上取决于实际情况。

    【讨论】:

      【解决方案4】:

      一种可能的方法是对第二列使用过滤。不幸的是,仅从 3.6 开始支持对聚类列进行过滤。因此,如果您的版本小于该版本但为 3.0.x 版本,您可以通过将第二个集群列转换为常规列来解决您的问题。在这种情况下,以下查询将为您提供您期望的结果:

      SELECT * FROM foo WHERE part_key='B' AND start_ts = 7 ALLOW FILTERING
      

      现在,我不知道您的数据及其基数,因此过滤可能不是一个好的选择。

      另一种选择是更改您的数据模型。有不同的方法可以对事物进行建模,以高效地满足您的需求。

      【讨论】:

        【解决方案5】:

        这完全是关于元组的 ORDER,而不是各个列的 VALUE。 简单来说,这不是比较值,而是比较元组的顺序。一旦 Cassandra 根据您的聚类顺序对元组进行排序,它只会比较元组的顺序。例如,在给定集合的分区 B 中: (start_ts, end_ts) >= (1, 1) AND (start_ts, end_ts)

        此逻辑与的第一部分:(start_ts, end_ts) >= (1, 1) 将包括此 (1,1) 元组以下的所有元组:(1,2), (4,7), (9 ,13), (9,9999)

        此逻辑与的第二部分:(start_ts, end_ts)

        由于这种设计,总是需要对代码进行进一步检查。例如,如果您的表具有开始日期和结束日期列,并且您已使用 StartDate ASC 和 EndDate ASC 对其进行聚类;并且您正在寻找 FromDate-ToDate 范围之间的所有行,您将始终需要进一步过滤代码中的行。为此,在 where 子句中,您将拥有: (StartDate,EndDate)>=(FromDate,FromDate) AND (StartDate,EndDate)

        这不等同于 SQL 条件: StartDate>=FromDate AND EndDate

        希望这会有所帮助。

        【讨论】:

          猜你喜欢
          • 2018-09-01
          • 2016-05-03
          • 2017-03-14
          • 2013-09-04
          • 1970-01-01
          • 1970-01-01
          • 2018-03-11
          • 2013-11-03
          • 2016-10-31
          相关资源
          最近更新 更多