范围查询在卡桑德拉

时间:2015-02-03 16:30:08

标签: java cassandra datastax-java-driver

我正在使用Cassandra 2.1.2和相应的DataStax Java驱动程序以及DataStax提供的Object映射。

下表定义:

CREATE TABLE IF NOT EXISTS ses.tim (id text PRIMARY KEY, start bigint, cid int);

映射:

@Table(keyspace = "ses", name = "tim")
class MyObj {
    @PartitionKey
    private String id;
    private Long start;
    private int cid;
}

访问者

@Accessor
interface MyAccessor {
    @Query("SELECT * FROM ses.tim WHERE id = :iid")
    MyObj get(@Param("iid") String id);

    @Query(SELECT * FROM ses.tim WHERE start <= :sstart")
    Result<MyObj> get(@Param("sstart") long start);
}

如访问者中所示,我想进行一个查询,返回“start”小于或等于特定值的所有内容。

有了这个表的定义,根本不可能。因此我尝试创建二级索引:

CREATE INDEX IF NOT EXISTS myindex ON ses.tim (start);

这似乎行不通(我读了很多解释为什么它决定不支持这个,但我仍然不明白为什么有人会给出这样的限制,无论如何......)

所以,据我所知,我们必须在WHERE子句中至少有一个等号

@Query(SELECT * FROM ses.tim WHERE cid = :ccid AND start <= :sstart")

CREATE INDEX IF NOT EXISTS myindex2 ON ses.tim (cid);

如果这样可行,我将不得不知道cid的所有可能值,并分别查询它们并在客户端上完成其余的...但我得到的错误是

Cannot execute this query as it might involve data filtering and thus may have unpredictable performance

然后我试了

id text, start bigint, cid int, PRIMARY KEY (id, start, cid)

@Table(keyspace = "ses", name = "tim")
class MyObj {
    @PartitionKey
    private String id;
    @ClusteringColumn(0)
    private Long start;
    @ClusteringColumn(1)
    private int cid;
}

但仍然没有运气。

此外,我尝试将'start'设置为PartitionKey,但只能再次使用Equals进行查询...

我错过了什么?如何获得此类查询的结果?

编辑:版本已更新以更正

2 个答案:

答案 0 :(得分:5)

如果您对同一组数据有不同的查询能力需求,则可以考虑对数据进行非规范化。根据您的问题,听起来您需要以下内容:

  • id
  • 查询
  • start查询&lt; X

当您使用当前架构指示时,第一个查询正常工作。但是,第二个查询无法正常工作,因为没有二级索引会因为您已经调查过的原因而变慢(我总是针对二级索引指向this blog post

您表示您不想在cid上进行分区,因为您需要了解cid的所有可能值。

我能想到的三个想法:

  • 使用虚拟主键创建单独的表,以便将所有数据存储在同一分区中。如果您有许多条目在任何保存该数据的节点上创建超宽分区和热点,则这可能会有问题。你有多少计划?

    create table if not exists tim (
        dummy int, 
        start bigint, 
        id text, 
        cid int, 
        primary key (dummy, start)
    );
    

    然后您可以进行如下查询:

    select * from tim where dummy=0 and start <= 10;
    
  • 另一个选项是在原始表上使用ALLOW FILTERING,它仍会进行昂贵的范围查询并过滤数据。

    select * from tim where start <= 10 ALLOW FILTERING;
    
  • 另一种选择是使用spark-connector之类的东西创建一个生成查询的spark作业。连接器会将昂贵的范围查询分解为较小的任务,并将数据映射到RDD,从而可以灵活地进行具有良好性能的更复杂查询。

答案 1 :(得分:3)

  

我正在使用Cassandra 2.1.3

我不认为2.1.3已经发布。 project site目前显示2.1.2为最高版本。

从我所看到的,您的主要问题是您的分区键id要么是唯一的,要么基数太高而不适合您。目前,您正在采用RDBMS风格的方法来存储数据(通过唯一ID)。使用Cassandra,您希望以一种有意义的方式存储数据以进行查询。第一步,就是选择一个好的密钥来对数据进行分区。

  

因此我尝试创建二级索引

你不想在这里做的另一件事是使用二级索引。我可以看到你很想这样做,你应该马上把这个想法从头脑中解脱出来。为方便起见创建了二级索引。它们不是为了提高性能而创建的,也不是为了在数据模型上采用快捷方式而创建的。

Cannot execute this query as it might involve data filtering and thus
may have unpredictable performance.

说到诱惑,在看到此消息时,您可能会考虑尝试在查询中添加ALLOW FILTERING。绝对不要那样做。它平坦地警告你,这样做不会很好,你应该留意那个警告。

  

如果这样可行,我将不得不知道cid的所有可能值,并分别查询它们并在客户端上完成其余的工作。

cid有多独特?如果必须获取并遍历所有cid s太麻烦,那么您应该考虑选择/创建一个不太独特的值来进行分区。但是,假设cid将起作用,这就是表定义的外观:

CREATE TABLE IF NOT EXISTS ses.tim 
(cid int,
 start bigint,
 id text,
 PRIMARY KEY ((cid),start);

@Table(keyspace = "ses", name = "tim")
class MyObj {
    @PartitionKey
    private int cid;
    @ClusteringColumn(0)
    private Long start;
    private String id;
}

使用此基础表定义,此查询现在应该可以正常工作。

@Query("SELECT * FROM ses.tim WHERE cid = :ccid AND start <= :sstart")

为您的数据模型提供另一种外观,并且(如果cid不是非常独特),看看您是否可以提供更好的列来对数据进行分组。有关更多信息,请阅读Patrick McFadin的文章Getting Started With Time Series Data Modeling。他讨论了一些与你的用例有些相似的用例,可能会指出你正确的方向。