cassandra数据表的设计

本文涉及的产品
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
云原生数据库 PolarDB MySQL 版,通用型 2核8GB 50GB
简介: 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数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
NoSQL MongoDB 索引
带着问题看 MongoDB——collStats 和 dbStats 命令中的各种 size 怎么理解
MongoDB 底层使用了 WiredTiger 存储引擎,WT 使用的块分配策略会产生磁盘碎片,通过理解collStats 和 dbStats 命令中的各种 size,最后我们看下真正的磁盘碎片率怎么计算,是否需要做 compact。
3715 0
|
NoSQL MongoDB
MongoDB compact 命令详解
为什么需要 compact 一图胜千言 remove 与 drop 的区别 MongoDB 里删除一个集合里所有文档,有两种方式 db.collection.remove({}, {multi: true}),逐个文档从 btree 里删除,最后所有文档被删除,但文件物理空间不会被回收 db.
|
消息中间件 SQL 存储
超详细的RabbitMQ入门,看这篇就够了!
RabbitMQ入门,看这篇就够了
217243 68
|
NoSQL Java
简析Cassandra的BATCH操作
cassandra中批量写入的操作称为batch,通过batch操作可以将多个写入请求合并为一个请求。这样有如下作用: 把多次更新操作合并为一次请求,减少客户端和服务端的网络交互。 batch中同一个partition key的操作具有隔离性。
6969 0
|
12月前
|
SQL NoSQL 数据库
Cassandra数据库与Cql实战笔记
Cassandra数据库与Cql实战笔记
176 1
Cassandra数据库与Cql实战笔记
|
存储 NoSQL Java
实现Spring Boot与Apache Cassandra的数据存储整合
实现Spring Boot与Apache Cassandra的数据存储整合
|
缓存 运维 监控
Cassandra 性能压测及调优实战
掌握Cassandra分布式数据库性能压测及性能调优 作者:孤池
4298 1
Cassandra 性能压测及调优实战
|
NoSQL Oracle 关系型数据库
cassandra使用场景判断:何时使用及何时不用
介绍 我有一个具有以下功能的数据库服务器: 高可用设计。 可以全球分布。 允许应用程序随时随地写入任何节点。 只需向群集添加更多节点即可进行线性扩展。 自动负载及数据均衡。 一种看起来很像SQL的查询语言。
9263 1
|
存储 NoSQL Java
Spring Boot与Cassandra数据库的集成应用
Spring Boot与Cassandra数据库的集成应用
|
SQL 存储 JSON
Cassandra CQL语法 以及功能介绍
内容摘要: 一、Cassandra简单介绍 ·Cassandra的历史和基本架构 二、Cassandra CQL介绍 ·如何使用CQL语言操作Cassandra
4657 0
Cassandra CQL语法 以及功能介绍