Cassandra 文档

版本

您正在查看预发布版本的文档。

数据类型

CQL 是一种类型化语言,支持丰富的數據類型,包括 原生类型集合类型用户定义类型元组类型自定义类型

cql_type::= native_type| collection_type| user_defined_type | tuple_type | custom_type

原生类型

CQL 支持的原生类型为

native_type::= ASCII | BIGINT | BLOB | BOOLEAN | COUNTER | DATE
| DECIMAL | DOUBLE | DURATION | FLOAT | INET | INT |
SMALLINT | TEXT | TIME | TIMESTAMP | TIMEUUID | TINYINT |
UUID | VARCHAR | VARINT | VECTOR

下表提供了有关原生数据类型以及每种类型支持的 常量 类型的更多信息

类型 支持的常量 描述

ascii

string

ASCII 字符串

bigint

integer

64 位有符号长整型

blob

blob

任意字节(无验证)

boolean

boolean

truefalse

counter

integer

计数器列(64 位有符号值)。有关详细信息,请参阅 counters

date

integerstring

日期(没有对应的时间值)。有关详细信息,请参阅下面的 dates

decimal

integerfloat

可变精度十进制

double

integer float

64 位 IEEE-754 浮点数

duration

duration,

具有纳秒精度的持续时间。有关详细信息,请参阅下面的 durations

float

integerfloat

32 位 IEEE-754 浮点数

inet

string

IP 地址,可以是 IPv4(4 个字节长)或 IPv6(16 个字节长)。请注意,没有 inet 常量,IP 地址应作为字符串输入。

int

integer

32 位有符号整型

smallint

integer

16 位有符号整型

text

string

UTF8 编码字符串

time

integerstring

时间(没有对应的日期值),具有纳秒精度。有关详细信息,请参阅下面的 times

timestamp

integerstring

时间戳(日期和时间),具有毫秒精度。有关详细信息,请参阅下面的 timestamps

timeuuid

uuid

版本 1 UUID,通常用作“无冲突”时间戳。另请参阅 timeuuid-functions

tinyint

integer

8 位有符号整型

uuid

uuid

UUID(任何版本)

varchar

string

UTF8 编码字符串

varint

integer

任意精度整数

vector

float

固定长度非空,扁平化的浮点值数组 CASSANDRA-18504 将此数据类型添加到 Cassandra 5.0。

计数器

counter 类型用于定义计数器列。计数器列是其值为 64 位有符号整数的列,并且支持两种操作:递增和递减(有关语法,请参阅 UPDATE 语句)。请注意,无法设置计数器的值:计数器在第一次递增/递减之前不存在,并且第一次递增/递减被视为先前值为 0。

计数器有一些重要的限制

  • 它们不能用于表 PRIMARY KEY 的一部分的列。

  • 包含计数器的表只能包含计数器。换句话说,表的 PRIMARY KEY 之外的所有列要么具有 counter 类型,要么都没有。

  • 计数器不支持 过期.

  • 支持删除计数器,但仅保证第一次删除计数器时有效。换句话说,您不应该重新更新已删除的计数器(如果您这样做,则不能保证正确行为)。

  • 计数器更新本质上不是 幂等的。一个重要的结果是,如果计数器更新意外失败(超时或与协调器节点的连接丢失),客户端无法知道更新是否已应用。特别是,重放更新可能会或可能不会导致计数过高。

使用时间戳

timestamp 类型的值被编码为 64 位有符号整数,表示自标准基准时间(称为 纪元)以来的毫秒数:格林威治标准时间 1970 年 1 月 1 日 00:00:00。

时间戳可以在 CQL 中使用其值作为 integer 输入,也可以使用表示 ISO 8601 日期 的 string 输入。例如,以下所有值都是格林威治标准时间 2011 年 3 月 2 日上午 04:05:00 的有效 timestamp

  • 1299038700000

  • '2011-02-03 04:05+0000'

  • '2011-02-03 04:05:00+0000'

  • '2011-02-03 04:05:00.000+0000'

  • '2011-02-03T04:05+0000'

  • '2011-02-03T04:05:00+0000'

  • '2011-02-03T04:05:00.000+0000'

上面的 +0000 是 RFC 822 4 位时区规范;+0000 指的是格林威治标准时间。美国太平洋标准时间为 -0800。如果需要,可以省略时区('2011-02-03 04:05:00'),如果是这样,则日期将被解释为协调 Cassandra 节点配置的时区。但是,依赖于时区配置是否如预期的那样存在固有的困难,因此建议在可行的情况下始终为时间戳指定时区。

也可以省略一天中的时间('2011-02-03''2011-02-03+0000'),在这种情况下,一天中的时间将默认为指定或默认时区的 00:00:00。但是,如果只与日期部分相关,请考虑使用 date 类型。

日期类型

date 类型的值被编码为 32 位无符号整数,表示以“纪元”为中心的范围内的天数(2^31)。纪元是 1970 年 1 月 1 日。

对于 时间戳,日期可以作为 integer 输入,也可以使用日期 string 输入。在后一种情况下,格式应为 yyyy-mm-dd(例如 '2011-02-03')。

时间类型

time 类型的值被编码为 64 位有符号整数,表示自午夜以来的纳秒数。

对于 时间戳,时间可以作为 integer 输入,也可以使用表示时间的 string 输入。在后一种情况下,格式应为 hh:mm:ss[.fffffffff](其中亚秒精度是可选的,如果提供,可以小于纳秒)。因此,例如,以下时间是有效的输入

  • '08:12:54'

  • '08:12:54.123'

  • '08:12:54.123456'

  • '08:12:54.123456789'

持续时间类型

duration 类型的值被编码为 3 个可变长度的有符号整数。第一个整数表示月份数,第二个表示天数,第三个表示纳秒数。这是因为一个月的日数可能会变化,并且一天可能有 23 或 25 个小时,具体取决于夏令时。在内部,月份数和天数被解码为 32 位整数,而纳秒数被解码为 64 位整数。

持续时间可以作为以下内容输入:

  • (quantity unit)+,例如 12h30m,其中单位可以是

    • y:年(12 个月)

    • mo:月(1 个月)

    • w:周(7 天)

    • d:天(1 天)

    • h:小时(3,600,000,000,000 纳秒)

    • m:分钟(60,000,000,000 纳秒)

    • s:秒(1,000,000,000 纳秒)

    • ms:毫秒(1,000,000 纳秒)

    • usµs:微秒(1000 纳秒)

    • ns:纳秒(1 纳秒)

  • ISO 8601 格式:P[n]Y[n]M[n]DT[n]H[n]M[n]S 或 P[n]W

  • ISO 8601 备用格式:P[YYYY]-[MM]-[DD]T[hh]:[mm]:[ss]

例如

INSERT INTO RiderResults (rider, race, result)
   VALUES ('Christopher Froome', 'Tour de France', 89h4m48s);
INSERT INTO RiderResults (rider, race, result)
   VALUES ('BARDET Romain', 'Tour de France', PT89H8M53S);
INSERT INTO RiderResults (rider, race, result)
   VALUES ('QUINTANA Nairo', 'Tour de France', P0000-00-00T89:09:09);

持续时间列不能用于表的 PRIMARY KEY 中。此限制是由于持续时间无法排序。在没有日期上下文的情况下,实际上无法知道 1mo 是否大于 29d

1d 持续时间不等于 24h 持续时间,因为持续时间类型已创建为能够支持夏令时。

集合

CQL 支持三种类型的集合:mapssetslists。这些集合的类型由

collection_type::= MAP '<' cql_type',' cql_type'>'
	| SET '<' cql_type '>'
	| LIST '<' cql_type'>'

定义,它们的值可以使用集合字面量输入

collection_literal::= map_literal | set_literal | list_literal
map_literal::= '\{' [ term ':' term (',' term : term)* ] '}'
set_literal::= '\{' [ term (',' term)* ] '}'
list_literal::= '[' [ term (',' term)* ] ']'

但是请注意,集合字面量中不支持 bind_markerNULL

值得注意的特征

集合用于存储/反规范化相对少量的数据。它们适用于诸如“给定用户的电话号码”、“应用于电子邮件的标签”等情况。但是,当项目预计会无限制地增长(“用户发送的所有消息”、“传感器注册的事件”……)时,集合就不合适了,应该使用特定的表(带有聚类列)。具体来说,(非冻结)集合具有以下值得注意的特征和限制

  • 单个集合在内部没有索引。这意味着即使要访问集合中的单个元素,也必须读取整个集合(并且内部不会对读取进行分页)。

  • 虽然集合和映射上的插入操作在内部永远不会产生读前写,但列表上的某些操作会产生读前写。此外,某些列表操作本质上不是幂等的(有关详细信息,请参见下面关于列表的部分),这使得在超时的情况下重试它们成为问题。因此,建议尽可能地优先使用集合而不是列表。

请注意,虽然这些限制中的一些可能在将来被移除/改进,但使用(单个)集合来存储大量数据是一种反模式。

映射

一个map是一个(排序的)键值对集合,其中键是唯一的,并且映射按其键排序。您可以使用以下方法定义和插入映射

CREATE TABLE users (
   id text PRIMARY KEY,
   name text,
   favs map<text, text> // A map of text keys, and text values
);

INSERT INTO users (id, name, favs)
   VALUES ('jsmith', 'John Smith', { 'fruit' : 'Apple', 'band' : 'Beatles' });

// Replace the existing map entirely.
UPDATE users SET favs = { 'fruit' : 'Banana' } WHERE id = 'jsmith';

此外,映射支持

  • 更新或插入一个或多个元素

    UPDATE users SET favs['author'] = 'Ed Poe' WHERE id = 'jsmith';
    UPDATE users SET favs = favs + { 'movie' : 'Cassablanca', 'band' : 'ZZ Top' } WHERE id = 'jsmith';
  • 删除一个或多个元素(如果元素不存在,删除它是一个无操作,但不会抛出错误)

    DELETE favs['author'] FROM users WHERE id = 'jsmith';
    UPDATE users SET favs = favs - { 'movie', 'band'} WHERE id = 'jsmith';

    请注意,要从map中删除多个元素,您需要从它中删除一个set的键。

最后,TTL 允许用于INSERTUPDATE,但在两种情况下,设置的 TTL 仅适用于新插入/更新的元素。换句话说

UPDATE users USING TTL 10 SET favs['color'] = 'green' WHERE id = 'jsmith';

只会将 TTL 应用于{ 'color' : 'green' } 记录,映射的其余部分不受影响。

集合

一个set是一个(排序的)唯一值的集合。您可以使用以下方法定义和插入集合

CREATE TABLE images (
   name text PRIMARY KEY,
   owner text,
   tags set<text> // A set of text values
);

INSERT INTO images (name, owner, tags)
   VALUES ('cat.jpg', 'jsmith', { 'pet', 'cute' });

// Replace the existing set entirely
UPDATE images SET tags = { 'kitten', 'cat', 'lol' } WHERE name = 'cat.jpg';

此外,集合支持

  • 添加一个或多个元素(因为这是一个集合,插入一个已经存在的元素是一个无操作)

    UPDATE images SET tags = tags + { 'gray', 'cuddly' } WHERE name = 'cat.jpg';
  • 删除一个或多个元素(如果元素不存在,删除它是一个无操作,但不会抛出错误)

    UPDATE images SET tags = tags - { 'cat' } WHERE name = 'cat.jpg';

最后,对于集合,TTL 仅适用于新插入的值。

列表

如上所述,并在本节末尾进一步讨论,列表具有限制和特定的性能注意事项,您在使用它们之前应该考虑这些因素。一般来说,如果您可以使用集合而不是列表,始终优先使用集合。

一个list是一个(排序的)非唯一值的集合,其中元素按它们在列表中的位置排序。您可以使用以下方法定义和插入列表

CREATE TABLE plays (
    id text PRIMARY KEY,
    game text,
    players int,
    scores list<int> // A list of integers
)

INSERT INTO plays (id, game, players, scores)
           VALUES ('123-afde', 'quake', 3, [17, 4, 2]);

// Replace the existing list entirely
UPDATE plays SET scores = [ 3, 9, 4] WHERE id = '123-afde';

此外,列表支持

  • 将值追加和预置到列表中

    UPDATE plays SET players = 5, scores = scores + [ 14, 21 ] WHERE id = '123-afde';
    UPDATE plays SET players = 6, scores = [ 3 ] + scores WHERE id = '123-afde';
警告

追加和预置操作本质上不是幂等的。因此,特别是如果这些操作之一超时,则重试该操作是不安全的,它可能(也可能不)导致将值追加/预置两次。

  • 设置列表中特定位置的值,该位置具有该位置的预先存在的元素。如果列表没有该位置,则会抛出错误

    UPDATE plays SET scores[1] = 7 WHERE id = '123-afde';
  • 通过列表中特定位置的元素删除元素,该位置具有该位置的预先存在的元素。如果列表没有该位置,则会抛出错误。此外,由于该操作从列表中删除了一个元素,因此列表的大小将减少一个元素,将所有后续元素的位置向前移动一个位置

    DELETE scores[1] FROM plays WHERE id = '123-afde';
  • 删除列表中特定值的所有出现(如果特定元素在列表中根本没有出现,则它将被简单地忽略,不会抛出错误)

    UPDATE plays SET scores = scores - [ 12, 21 ] WHERE id = '123-afde';
警告

通过位置设置和删除元素以及删除特定值的出现会产生内部读前写。这些操作的运行速度会很慢,并且会比通常的更新使用更多资源(除了具有自身成本的条件写入)。

最后,对于列表,TTL 仅适用于新插入的值。

使用向量

向量是固定大小的非空值的序列,这些值具有特定数据类型。它们使用与列表相同的文字。

您可以使用以下方法定义、插入和更新向量

CREATE TABLE plays (
    id text PRIMARY KEY,
    game text,
    players int,
    scores vector<int, 3> // A vector of 3 integers
)

INSERT INTO plays (id, game, players, scores)
           VALUES ('123-afde', 'quake', 3, [17, 4, 2]);

// Replace the existing vector entirely
UPDATE plays SET scores = [ 3, 9, 4] WHERE id = '123-afde';

请注意,无法更改向量的单个值,也无法选择向量的单个元素。

用户定义类型 (UDT)

CQL 支持用户定义类型 (UDT) 的定义。可以使用下面描述的create_type_statementalter_type_statementdrop_type_statement 创建、修改和删除此类类型。但是,一旦创建,UDT 只通过其名称引用

user_defined_type::= udt_name
udt_name::= [ keyspace_name '.' ] identifier

创建 UDT

使用CREATE TYPE 语句创建新的用户定义类型,该语句由以下内容定义

create_type_statement::= CREATE TYPE [ IF NOT EXISTS ] udt_name
        '(' field_definition ( ',' field_definition)* ')'
field_definition::= identifier cql_type

UDT 具有一个名称(用于声明该类型的列),并且是一组命名和类型化的字段。字段名称可以是任何类型,包括集合或其他 UDT。例如

CREATE TYPE phone (
    country_code int,
    number text,
);

CREATE TYPE address (
    street text,
    city text,
    zip text,
    phones map<text, phone>
);

CREATE TABLE user (
    name text PRIMARY KEY,
    addresses map<text, frozen<address>>
);

关于 UDT 的注意事项

  • 尝试创建已经存在的类型会导致错误,除非使用IF NOT EXISTS 选项。如果使用它,如果类型已经存在,则该语句将是一个无操作。

  • 类型本质上绑定到创建它的键空间,并且只能在该键空间中使用。在创建时,如果类型名称以键空间名称为前缀,则它将在该键空间中创建。否则,它将在当前键空间中创建。

  • 从 Cassandra 开始,UDT 在大多数情况下必须被冻结,因此在上面的表定义中使用frozen<address>

UDT 文字

一旦创建了用户定义类型,就可以使用 UDT 文字输入值

udt_literal::= '{' identifier ':' term ( ',' identifier ':' term)* '}'

换句话说,UDT 文字就像映射` 文字,但其键是类型的字段名称。例如,可以使用以下方法将数据插入到上一节中定义的表中

INSERT INTO user (name, addresses)
   VALUES ('z3 Pr3z1den7', {
     'home' : {
        street: '1600 Pennsylvania Ave NW',
        city: 'Washington',
        zip: '20500',
        phones: { 'cell' : { country_code: 1, number: '202 456-1111' },
                  'landline' : { country_code: 1, number: '...' } }
     },
     'work' : {
        street: '1600 Pennsylvania Ave NW',
        city: 'Washington',
        zip: '20500',
        phones: { 'fax' : { country_code: 1, number: '...' } }
     }
  }
);

要使 UDT 文字有效,它只能包含类型定义的字段,但它可以省略某些字段(这些字段将被设置为NULL)。

更改 UDT

可以使用ALTER TYPE 语句修改现有的用户定义类型

alter_type_statement::= ALTER TYPE [ IF EXISTS ] udt_name alter_type_modification
alter_type_modification::= ADD [ IF NOT EXISTS ] field_definition
        | RENAME [ IF EXISTS ] identifier TO identifier (AND identifier TO identifier )*

如果类型不存在,该语句将返回错误,除非使用IF EXISTS,在这种情况下,该操作是一个无操作。您可以

  • 向类型添加新字段(ALTER TYPE address ADD country text)。对于在添加之前创建的类型的任何值,该新字段将为NULL。如果新字段存在,该语句将返回错误,除非使用IF NOT EXISTS,在这种情况下,该操作是一个无操作。

  • 重命名类型的字段。如果字段不存在,该语句将返回错误,除非使用IF EXISTS,在这种情况下,该操作是一个无操作。

ALTER TYPE address RENAME zip TO zipcode;

删除 UDT

您可以使用DROP TYPE 语句删除现有的用户定义类型

drop_type_statement::= DROP TYPE [ IF EXISTS ] udt_name

删除类型会导致立即、不可逆地删除该类型。但是,尝试删除仍在其他类型、表或函数中使用的类型会导致错误。

如果删除的类型不存在,将返回错误,除非使用IF EXISTS,在这种情况下,该操作是一个无操作。

元组

CQL 还支持元组和元组类型(其中元素可以是不同类型)。在功能上,元组可以被认为是具有匿名字段的匿名 UDT。元组类型和元组文字由以下内容定义

tuple_type::= TUPLE '<' cql_type( ',' cql_type)* '>'
tuple_literal::= '(' term( ',' term )* ')'

并且可以创建

CREATE TABLE durations (
  event text,
  duration tuple<int, text>,
);

INSERT INTO durations (event, duration) VALUES ('ev1', (3, 'hours'));

与其他复合类型(如集合和 UDT)不同,元组始终为frozen <frozen>(无需使用frozen 关键字),并且无法仅更新元组的某些元素(无需更新整个元组)。此外,元组文字应始终具有与它所属的类型中声明的相同数量的值(其中一些值可以为空,但需要显式声明为这样)。

自定义类型

自定义类型主要用于向后兼容目的,不鼓励使用它们。它们的用法很复杂,不友好,其他提供的类型,特别是用户定义类型,几乎总是足够了。

自定义类型由以下内容定义

custom_type::= string

自定义类型是一个string,它包含扩展服务器端AbstractType 类的 Java 类的名称,并且可以由 Cassandra 加载(因此它应该位于运行 Cassandra 的每个节点的CLASSPATH 中)。该类将定义哪些值为类型有效以及在用作聚类列时时间如何排序。对于任何其他目的,自定义类型的值与blob 的值相同,并且可以特别地使用blob 文字语法输入。