cassandra数据表的设计

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
简介: cassandra数据作为一个非常类似于关系型数据库的nosql,其在数据表的设计上于关系型数据库有着一些不同的地方。本文尽个人所理解的内容做简单的剖析。 Primary Key     关系型数据的Primary Key主要是作为数据的唯一性标识用的,对具体业务并没有太大的帮助,比如大量使用的自增id作为唯一主键,而这个id实际上并不是业务的数据,只是作为一个技术上保证唯一性标识的手段而已。

cassandra数据作为一个非常类似于关系型数据库的nosql,其在数据表的设计上于关系型数据库有着一些不同的地方。本文尽个人所理解的内容做简单的剖析。


Primary Key

    关系型数据的Primary Key主要是作为数据的唯一性标识用的,对具体业务并没有太大的帮助,比如大量使用的自增id作为唯一主键,而这个id实际上并不是业务的数据,只是作为一个技术上保证唯一性标识的手段而已。

    cassandra的Primary key也要保证数据的唯一性标识,但是其更大的作用是服务于查询,也就是说cassandra的主键更多是面向于业务来设计的,值得注意的是cassandra的表的primary key并不能动态改变,所以很多设计者犯得一个错误就是要查询的字段并没有在primary key中导致只能开启全表扫描查询,这是一种不可取的办法,所以在设计之初就应该避免这种情况。


复合主键

    cassandra主键包含了两个部分,partition key和cluster key。在说明这两个key之前需要简单的介绍一下cassandra的一些基本特性,在关系数据库的结构中当数据量增多的时候回导致单个表的数据查询性能下降,设计者也缺少好的策略去自动分割这些大量的数据。

    cassandra则是采用partition来对于数据进行划分保存,在良好设计的情况下cassandra就会按照定义的策略自动对数据进行分区,而partition key就是cassandra分区的依据,通过一个一致性hash函数计算这个partition key的token进而直接找到其所在的分区。所以需要注意的是数据一旦写入分区就不能update partition key了,当你可以通过删除然后再添加来实现,同时这样附带了一个隐藏的问题,如果你的partition key有两个字段,当你在查询的时候你要么不指定分区,要么就必须明确指定分区。

表设计

    在这里以datastax的一个demo来展开说明。

race_year int,    #比赛的年份

race_name text, #比赛的名称

cyclist_name text, #选手名称

rank int, #名次


如果是在mysql中这是一个非常简单表,直接添加一个自增的主键id,然后把这四个字段加进去就可以了。

在cassandra中这个表的设计则变得有点复杂了,下面我们将参照不同的设计来分析所能应付的不同的业务。

在这之前我们要首先确保数据的唯一性,在某一年的某一场比赛中的第几名应该是可以确定到唯一的一条数据的。

假如表数据如下

CREATE TABLE rank_by_year_and_name ( race_year int, race_name text, cyclist_name text, rank int, PRIMARY KEY ((race_year),race_name,rank));

  race_year作为唯一的partition key,race_name和rank作为cluster key,我们可以说这个表将会按照每一个年份自动切分数据,而每一个分区内都会按照比赛名和名称做排序。

接下来我们做一些简单的练习。

 INSERT INTO rank_by_year_and_name(race_year, race_name, rank, cyclist_name)VALUES (2018, '青海自行车赛', 2,'李明');

INSERT INTO rank_by_year_and_name(race_year, race_name, rank, cyclist_name)VALUES (2018, '青海自行车赛', 1,'李明');

INSERT INTO rank_by_year_and_name(race_year, race_name, rank, cyclist_name)VALUES (2018, '青海自行车赛', 2,'李明');

INSERT INTO rank_by_year_and_name(race_year, race_name, rank, cyclist_name)VALUES (2018, '青海自行车赛', 1,'李明');

INSERT INTO rank_by_year_and_name(race_year, race_name, rank, cyclist_name)VALUES (2019, '青海自行车赛', 1,'李明');

UPDATE rank_by_year_and_name SETcyclist_name = '博尔特'WHERErace_year = 223 ANDrace_name = '环法自行车赛' AND rank = 1 IF EXISTS;

select * from rank_by_year_and_name;

select * from rank_by_year_and_name where race_year=2018;

select * from rank_by_year_and_name where race_name='青海自行车赛';

select * from rank_by_year_and_name where rank=1;

select * from rank_by_year_and_name where race_name='青海自行车赛' and rank=1 and race_year=2018;

select * from rank_by_year_and_name where race_name='青海自行车赛' and rank=1 and race_year>=2018;

select * from rank_by_year_and_name where race_year=2018 and race_name='青海自行车赛' and rank>0;

select * from rank_by_year_and_name where race_year=2018 and rank>0;

select * from rank_by_year_and_name where race_name='青海自行车赛' and race_year in (2018,2019);

下面选择几个特别的查询做特殊说明

  1. 重复插入不提示

   第二句INSERT INTO rank_by_year_and_name(race_year, race_name, rank, cyclist_name)VALUES (2018, '青海自行车赛', 2,'李明');

  可以看到当数据重复的时候cassandra不做任何提示. 

  1. upsert当where条件不存在直接插入数据.
      UPDATE rank_by_year_and_name SETcyclist_name = '博尔特'WHERErace_year = 222 ANDrace_name = '环法自行车赛' ANDrank = 1;

        使用if exists可以防止update的时候变成插入

UPDATE rank_by_year_and_name SETcyclist_name = '博尔特'WHERErace_year = 223 ANDrace_name = '环法自行车赛' ANDrank = 1 IF EXISTS;
  1. 不指定partition key无法查询

select * from rank_by_year_and_name where race_name='青海自行车赛';

select * from rank_by_year_and_name where rank=1;

这样就意味着说我们查询只能在指定的partition key上操作,某种程度上这就是直接避免了关系型数据库中的全表扫描。

  1. Partition key的范围查询
    select * from rank_by_year_and_name where race_name='青海自行车赛' and rank=1 and race_year>=2018;
        select * from rank_by_year_and_name where race_name='青海自行车赛' and race_year in (2018,2019);

    如果需要对多个分区进行操作,cassandra partition key不支持 >=这操作,尽管是int类型,而只能使用in查询。原因很简单,partition key是用于一致性hash计算得到token从而获取分区的,而hash是分散的所以不会随着partition key有大于小于的关系,但是in当然是可以了。

  1. Cluster key的顺序

select * from rank_by_year_and_name where race_year=2018 and rank>0;

select * from rank_by_year_and_name where race_year=2018 and race_name='青海自行车赛' ;

第二句可以执行,第一句错误,原因在cluster key是排序的,是有层级的,所以只能一层层的搜索。


总结

         Cassandra在查询的时候尽可能避免不明确的查询,查询必须制定partition key,如果想要在多个partition key查询,就要使用in来匹配多个,对于cluster key,层级关系也是很明确。Cassandra这样设计目的在于减少大面积的不确定的查询,在tb甚至更多的数据中做模糊查询可能会到来非常恶劣的效果。


非常差的表设计

CREATE TABLE rank_by_year_and_name ( race_year int, race_name text, cyclist_name text, rank int, PRIMARY KEY ((race_year)));


INSERT INTO rank_by_year_and_name(race_year, race_name, rank, cyclist_name)VALUES (2018, '青海自行车赛', 2,'李明');INSERT INTO rank_by_year_and_name(race_year, race_name, rank, cyclist_name)VALUES (2018, '青海自行车赛', 1,'李明');

上面的表问题在无法保证数据的唯一,数据会被覆盖,所以存在明显缺陷。


CREATE TABLE rank_by_year_and_name ( race_year int, race_name text, cyclist_name text, rank int, id int,PRIMARY KEY ((id)));



这个表几乎是直接挪用关系型数据库的表。

但是结果是由于要查询的字段都不在primary key,导致只能按照主键id查询,几乎等同于废表一张。


接下来可能你会天真说,那好我把所有的字段都放到primary key里。

CREATE TABLE rank_by_year_and_name ( race_year int, race_name text, cyclist_name text, rank int, id int,PRIMARY KEY ((id)),race_year,race_name,cyclist_name,rank);


但是别着急当你需要查询的需要制定一个partition key,而这个上面的表是一个id,当你按照某一个年份查询的时候你只能使用in对所有的partition key做扫描。这很明显又是一个非常差的选择。

  

       而且通过上面两个反面的例子,大家应该更能理解cassandra和关系型数据库在表设计上的差异,虽然不是十万百千里那么用,五万四千里还是有的,如果你直接套路关系型数据库的设计思路,那么得到的结果就是比用关系型数据库还要差。

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
存储 缓存 分布式计算
大数据开发笔记(十):Hbase列存储数据库总结
HBase 本质上是一个数据模型,可以提供快速随机访问海量结构化数据。利用 Hadoop 的文件系统(HDFS)提供的容错能 力。它是 Hadoop 的生态系统,使用 HBase 在 HDFS 读取消费/随机访问数据,是 Hadoop 文件系统的一部分。
887 0
大数据开发笔记(十):Hbase列存储数据库总结
|
4月前
|
NoSQL 分布式数据库 数据库
分布式NoSQL列存储数据库Hbase_列族的设计(五)
分布式NoSQL列存储数据库Hbase_列族的设计(五)
198 0
|
4月前
|
存储 NoSQL 分布式数据库
分布式NoSQL列存储数据库Hbase(一)Hbase的功能与应用场景、基本设计思想
分布式NoSQL列存储数据库Hbase(一)Hbase的功能与应用场景、基本设计思想
142 0
|
4月前
|
存储 NoSQL 分布式数据库
分布式NoSQL列存储数据库Hbase_高级思想(八)
分布式NoSQL列存储数据库Hbase_高级思想(八)
41 0
|
4月前
|
SQL 存储 NoSQL
分布式NoSQL列存储数据库Hbase操作(二)
分布式NoSQL列存储数据库Hbase操作(二)
114 0
|
9月前
|
存储 分布式计算 NoSQL
分布式数据库HBase的基本概念和架构之基本数据模型的Table
HBase是一个分布式数据库系统,基于Google的Bigtable和Apache Hadoop的HDFS构建而成。
|
9月前
|
存储 分布式计算 NoSQL
分布式数据库HBase的基本概念和架构之基本数据模型的Column
HBase是一个分布式数据库系统,基于Google的Bigtable和Apache Hadoop的HDFS构建而成。
229 0
|
10月前
|
存储 NoSQL 关系型数据库
【Cassandra从入门到放弃系列 三】Cassandra的数据模型设计
【Cassandra从入门到放弃系列 三】Cassandra的数据模型设计
210 0
|
11月前
|
存储 人工智能 大数据
Hbase中表结构的设计
Hbase中表结构的设计
|
存储 缓存 NoSQL
了解 kudu 数据模型 | 学习笔记
快速学习 了解 kudu 数据模型
190 0
了解 kudu 数据模型 | 学习笔记