【ScyllaDB】Data Manipulation
介绍CQL支持的用于插入、更新、删除和查询数据的语句。
SELECT
从data中查询数据使用 SELECT 语句完成:
select_statement: SELECT [ DISTINCT ] ( `select_clause` | '*' )
: FROM `table_name`
: [ WHERE `where_clause` ]
: [ GROUP BY `group_by_clause` ]
: [ ORDER BY `ordering_clause` ]
: [ PER PARTITION LIMIT (`integer` | `bind_marker`) ]
: [ LIMIT (`integer` | `bind_marker`) ]
: [ ALLOW FILTERING ]
: [ BYPASS CACHE ]
: [ USING TIMEOUT `timeout` ]
select_clause: `selector` [ AS `identifier` ] ( ',' `selector` [ AS `identifier` ] )*
selector: `column_name`
: | CAST '(' `selector` AS `cql_type` ')'
: | `function_name` '(' [ `selector` ( ',' `selector` )* ] ')'
: | COUNT '(' '*' ')'
where_clause: `relation` ( AND `relation` )*
relation: `column_name` `operator` `term`
: '(' `column_name` ( ',' `column_name` )* ')' `operator` `tuple_literal`
: TOKEN '(' `column_name` ( ',' `column_name` )* ')' `operator` `term`
operator: '=' | '<' | '>' | '<=' | '>=' | IN | CONTAINS | CONTAINS KEY
ordering_clause: `column_name` [ ASC | DESC ] ( ',' `column_name` [ ASC | DESC ] )*
timeout: `duration`
例如:
SELECT name, occupation FROM users WHERE userid IN (199, 200, 207);
SELECT name AS user_name, occupation AS user_occupation FROM users;
SELECT time, value
FROM events
WHERE event_type = 'myEvent'
AND time > '2011-02-03'
AND time <= '2012-01-01'
SELECT COUNT (*) AS user_count FROM users;
SELECT * FROM users WHERE event_type = 'myEvent' USING TIMEOUT 50ms;
SELECT 语句 为 表中的一行或多行 读取一个或多个列。它返回与请求匹配的行的结果集,其中每行包含与查询对应的选择的值。此外,可以将函数 (包括聚合函数) 应用于结果。
SELECT语句至少包含一个选择子句,以及选择所在表的名称 (注意,CQL不支持连接或子查询,因此选择语句仅适用于单个表) 。大多数情况下,一个select语句也会有一个 where 子句,并且它可以选择性地有额外的子句来 排序 或 限制 结果。最后,如果提供了 ALLOW FILTERING 标志,则可以使用 需要过滤的查询。
如果 SELECT 查询的结果缺少数据,请参阅这篇知识库文章了解相关信息。
Selection clause _ selection子句
select_clause 确定在结果集中需要查询和返回哪些列,以及在返回之前应用于该结果的任何转换。它由逗号分隔的选择器列表组成,或者由通配符 (*) 组成,用于选择表中定义的所有列。
Selectors
selector 可以是下列任意一种:
- 被选中检索该列的值的表的列名。
- 转换,它允许您将嵌套选择器转换为(兼容的)类型。
- 函数调用,其中参数是选择器本身。
- 对COUNT函数的调用,计算所有非空结果。
Aliases _ 别名
每个顶级选择器也可以别名 (使用AS)。如果是这样,结果集中对应的列的名称将是别名的名称。例如:
// Without alias
SELECT intAsBlob(4) FROM t;
// intAsBlob(4)
// --------------
// 0x00000004
// With alias
SELECT intAsBlob(4) AS four FROM t;
// four
// ------------
// 0x00000004
NOTE:
目前,在使用别名的语句中,别名在其他任何地方都无法被识别 (包括where子句,ORDER BY子句中,… ) 。必须使用原来的列名来代替。
WRITETIME 和 TTL 功能:
Selection 支持两个特殊的函数 (在其他地方不允许):WRITETIME 和 TTL。两个函数都只接受一个参数,并且该参数必须是一个列名 (因此,例如,TTL(3)无效)。
这些函数可以让你检索存储在每个列内部的元信息,即:
- WRITETIME 检索写列时使用的时间戳。时间戳通常是自Unix纪元(1970年1月1日00:00:00 UTC)以来的微秒数。
您可以在 UPDATE 小节中阅读更多关于 WRITETIME 获取的 TIMESTAMP 的信息。
- TTL检索列值的剩余生存时间(以秒为单位),如果它被设置为过期,否则为空。
您可以在 文档 和 Scylla大学课程 中阅读更多关于TTL的信息。
The WHERE clause
WHERE 子句指定必须查询哪些行。它由作为 PRIMARY KEY 一部分的列上的关系组成。
在查询中不是所有的关系都是允许的。例如,不支持分区键上的非相等关系 ( 其中 IN 被认为是相等关系 ) ( 参见下面使用 TOKEN 方法对分区键进行非相等查询) 。此外,对于给定的分区键,聚类列诱导行排序,并将其上的关系限制为允许您选择连续 (用于排序) 行的关系集。例如,给定:
CREATE TABLE posts (
userid text,
blog_title text,
posted_at timestamp,
entry_title text,
content text,
category int,
PRIMARY KEY (userid, blog_title, posted_at)
)
以下查询是合理的:
SELECT entry_title, content FROM posts
WHERE userid = 'john doe'
AND blog_title='John''s Blog'
AND posted_at >= '2012-01-01' AND posted_at < '2012-01-31'
但是下面的查询不是,因为它没有选择连续行 (我们假设没有设置二级索引) :
// Needs a blog_title to be set to select ranges of posted_at
SELECT entry_title, content FROM posts
WHERE userid = 'john doe'
AND posted_at >= '2012-01-01' AND posted_at < '2012-01-31'
在指定关系时,可以在 PARTITION KEY 列上使用 TOKEN 函数进行查询。在这种情况下,将根据其 PARTITION_KEY 的 token 而不是值来选择行。请注意,键的令牌 ( token ) 取决于所使用的分区器,特别是RandomPartitioner 不会产生有意义的顺序。还要注意,排序分区器总是按字节对令牌值排序 ( 特别是 token(-1) > token(0) ,即使分区键是int类型 )。例如:
SELECT * FROM posts
WHERE token(userid) > token('tom') AND token(userid) < token('bob')
而且,IN 关系只允许出现在 分区键的最后一列 和 完整主键的最后一列 上。
也可以使用 元组表示法 将 集群列 ( CLUSTERING COLUMNS )“分组”在一个关系中。例如:
SELECT * FROM posts
WHERE userid = 'john doe'
AND (blog_title, posted_at) > ('John''s Blog', '2012-01-01')
上述查询,将查询所有排在“John's Blog”为 blog_title 和 '2012-01-01'为 posted_at 的行之后的行。特别是,具有post_at <= '2012-01-01'的行 将返回 与它们的blog_title > ' John's Blog ' 一样长的行,这将不会是这种情况:
SELECT * FROM posts
WHERE userid = 'john doe'
AND blog_title > 'John''s Blog'
AND posted_at > '2012-01-01'
元组表示法也可用于集群列上的 IN 子句:
SELECT * FROM posts
WHERE userid = 'john doe'
AND (blog_title, posted_at) IN (('John''s Blog', '2012-01-01'), ('Extreme Chess', '2014-06-01'))
CONTAINS 操作符只能用于集合列 (列表、集合和映射)。在映射的情况下,CONTAINS 应用于映射值。CONTAINS KEY 操作符只能在映射列上使用,并应用于映射键。
Grouping results
Scylla Open Source 3.2 新版功能
GROUP BY 选项允许您将一组列中共享相同值的所有选定行压缩为一行。使用 GROUP BY 选项,只能在 分区键级别 或 集群列级别 对行进行分组。 GROUP BY 参数必须形成主键的前缀。
例如,如果主键是(p1, p2, c1, c2),那么以下查询是有效的:
GROUP BY p1
GROUP BY p1, p2
GROUP BY p1, p2, c1
GROUP BY p1, p2, c1, c2
在使用 GROUP BY 选项时,应考虑以下事项:
- 如果主键列受相等限制,则不需要出现在 GROUP BY 子句中。
- 聚合函数将为每个组生成一个单独的值。
- 如果没有指定 GROUP BY 子句,聚合函数将为所有行生成单个值。
- 如果选中的列没有指定聚合函数,则在带有 GROUP BY 的语句中,将返回每个组中遇到的第一个值。
Ordering results
ORDER BY 子句允许设置返回结果的顺序。它接受一个 列名列表 以及 列的顺序 作为参数 ( ASC 表示升序,DESC 表示降序,默认则为升序 )。目前,可能的排序结果会受到表上定义的聚类顺序的限制:
- 如果表的定义没有任何特定的 CLUSTERING ORDER,那么允许的顺序是由集群列指定的顺序或相反的顺序。(?)
- 否则,允许的排序是 CLUSTERING ORDER 选项的顺序和倒序的顺序。
Limiting results
在Scylla Open Source 3.1版更改
SELECT 语句的 LIMIT 选项限制查询返回的行数,而 PER PARTITION LIMIT 选项 (在Scylla 3.1中引入) 限制查询为给定分区返回的行数。请注意,这两种类型的限制都可以在同一条语句中使用。
例子:
下表中的分区键为 client_id ,集群键为when。表有7行,分为4个客户端(分区键)
cqlsh:ks1> SELECT client_id, when FROM test;
client_id | when
-----------+---------------------------------
1 | 2019-12-31 22:00:00.000000+0000
1 | 2020-01-01 22:00:00.000000+0000
2 | 2020-02-10 22:00:00.000000+0000
2 | 2020-02-11 22:00:00.000000+0000
2 | 2020-02-12 22:00:00.000000+0000
4 | 2020-02-10 22:00:00.000000+0000
3 | 2020-02-10 22:00:00.000000+0000
(7 rows)
你可以用 limit 命令来限制所有分区返回的行数,例如:
cqlsh:ks1> SELECT client_id, when FROM ks1.test LIMIT 3;
client_id | when
-----------+---------------------------------
1 | 2019-12-31 22:00:00.000000+0000
1 | 2020-01-01 22:00:00.000000+0000
2 | 2020-02-10 22:00:00.000000+0000
(3 rows)
您可以指定查询,限制每个查询返回的行数client_id。例如,limit为1:
cqlsh:ks1> SELECT client_id, when FROM ks1.test PER PARTITION LIMIT 1;
client_id | when
-----------+---------------------------------
1 | 2019-12-31 22:00:00.000000+0000
2 | 2020-02-10 22:00:00.000000+0000
4 | 2020-02-10 22:00:00.000000+0000
3 | 2020-02-10 22:00:00.000000+0000
(4 rows)
增加 limit 到2,将会得到:
cqlsh:ks1> SELECT client_id, when FROM ks1.test PER PARTITION LIMIT 2;
client_id | when
-----------+---------------------------------
1 | 2019-12-31 22:00:00.000000+0000
1 | 2020-01-01 22:00:00.000000+0000
2 | 2020-02-10 22:00:00.000000+0000
2 | 2020-02-11 22:00:00.000000+0000
4 | 2020-02-10 22:00:00.000000+0000
3 | 2020-02-10 22:00:00.000000+0000
(6 rows)
您还可以混合使用这两种限制类型:(每个分区限制1个,所有分区限制3个)
cqlsh> SELECT client_id, when FROM ks1.test PER PARTITION LIMIT 1 LIMIT 3;
client_id | when
-----------+---------------------------------
1 | 2019-12-31 22:00:00.000000+0000
2 | 2020-02-10 22:00:00.000000+0000
4 | 2020-02-10 22:00:00.000000+0000
(3 rows)
Allowing filtering
默认情况下,CQL只允许选择不涉及“过滤”服务器端的查询,即我们知道所有 (实时) 记录读取将在结果集中返回 (可能部分) 的查询。理由是,那些“非过滤”查询具有可预测的性能,因为它们将在与查询返回的数据量成正比的时间内执行 (可以通过LIMIT控制)。
ALLOW FILTERING 选项可以 显式地指定(某些)需要过滤的查询。请注意,使用 ALLOW FILTERING 的查询可能具有不可预测的性能 (对于上面的定义),即即使是选择少量记录的查询也可能显示依赖于集群中存储的数据总量的性能。
例如,考虑下面的表,其中包含用户的个人资料及其出生年份(带有二级索引)和居住国家:
CREATE TABLE users (
username text PRIMARY KEY,
firstname text,
lastname text,
birth_year int,
country text
)
CREATE INDEX ON users(birth_year);
以下查询是合法的:
SELECT * FROM users;
SELECT * FROM users WHERE birth_year = 1981;
因为在这两种情况下,Scylla 保证这些查询的性能与返回的数据量成正比。特别是,如果没有1981年出生的用户,那么第二次查询的性能将不会依赖于数据库中存储的用户配置文件的数量 (至少不会直接依赖:由于二级索引实现的考虑,这个查询可能仍然依赖于集群中的节点数量,而节点数量间接依赖于存储的数据量。尽管如此,节点的数量将始终比存储的用户配置文件的数量低多个数量级)。当然,这两种查询在实践中都可能返回非常大的结果集,但是返回的数据量总是可以通过添加 LIMIT 来控制的。
但是,下面的查询将会被拒绝:
SELECT * FROM users WHERE birth_year = 1981 AND country = 'FR';
因为 Scylla 不能保证它不需要扫描大量数据,即使这些查询的结果很小。通常情况下,它会扫描1981年出生的用户的所有索引条目,即使只有少数用户实际上来自法国。然而,如果你“知道你在做什么”,你可以通过使用ALLOW FILTERING
强制执行这个查询,所以下面的查询是有效的:
SELECT * FROM users WHERE birth_year = 1981 AND country = 'FR' ALLOW FILTERING;
Bypass Cache
Enterprise 2019.1.1,Scylla Open Source 3.1新特性
SELECT语句上的BYPASS CACHE
(旁路缓存)子句告诉数据库,正在读取的数据在不久的将来不太可能被再次读取,并且在不久的过去也不太可能被读取;因此,不应该尝试从缓存中读取它,也不应该用数据填充缓存。这主要用于范围扫描;这些通常会处理大量没有时间局部性的数据,并且不会从缓存中受益。该子句被紧接在可选的 ALLOW FILTERING 子句之后。
BYPASS CACHE
(旁路缓存)是Scylla CQL扩展,而不是Apache Cassandra CQL的一部分。
例如:
SELECT * FROM users BYPASS CACHE;
SELECT name, occupation FROM users WHERE userid IN (199, 200, 207) BYPASS CACHE;
SELECT * FROM users WHERE birth_year = 1981 AND country = 'FR' ALLOW FILTERING BYPASS CACHE;
Using Timeout
Scylla Open Source 4.4 新特性
USING TIMEOUT
子句为特定请求指定超时时间。例如:
SELECT * FROM users USING TIMEOUT 5s;
SELECT name, occupation FROM users WHERE userid IN (199, 200, 207) BYPASS CACHE USING TIMEOUT 200ms;
USING TIMEOUT
是Scylla CQL扩展,而不是Apache Cassandra CQL的一部分。
LIKE Operator
Scylla Open Source 3.2 新特性
SELECT
语句上的LIKE
操作告知Scylla,您正在进行模式匹配。表达式'column LIKE pattern'
只有在 整个列值 与 模式 匹配时才产生真值。
搜索模式是由两个通配符组成的字符串,如下所示:
_
匹配任意单个字符%
匹配任何子字符串 (包括空字符串)\
转义下一个模式字符,以便逐字匹配- 任何其他模式字符匹配自身
- 空模式匹配空文本字段
NOTE:
只有字符串类型(ascii、text和varchar)可以匹配
目前,匹配是区分大小写的。整个列值必须为匹配模式。例如,考虑搜索模式“M%n”——这将匹配Martin
,但不会匹配Moonbeam
,因为最后的m
不匹配。此外,由于M
与m
不相同,所以moon
不匹配。模式和列值都假定为UTF-8编码。
通过匹配适当的LIKE
模式,可以查询包含某些文本片段的所有值。
Scylla 和 Cassandra 中 LIKE 操作符的区别:
- 在 Apache Cassandra 中,必须创建一个 SASI 索引才能使用 LIKE。Scylla 支持LIKE 作为常规过滤器。
- 因此,在某些工作负载下, Scylla LIKE 的性能将低于 Apache Cassandra LIKE
- Scylla 将下划线
_
视为通配符;而Cassandra不是 - Scylla 将百分比
%
作为模式中的通配符;Cassandra只用于开头或结尾 - Scylla 将反斜杠
\
解释为转义字符;而Cassandra不是 - Cassandra允许大小写不敏感的 LIKE;而Scylla 不允许 (见#4911)。
- Scylla 允许空 LIKE 模式;而Cassandra不允许
Example A
在本例中,LIKE
指定匹配查找以字母S
开头的单词。字母S
后面的%
用于匹配到字段末尾的任何文本
SELECT * FROM pet_owners WHERE firstname LIKE ‘S%’ ALLOW FILTERING;
╭──────────┬─────────────────────┬────────────────╮
│ID │LastName │FirstName │
├──────────┼─────────────────────┼────────────────┤
│1 │Adams │Steven │
├──────────┼─────────────────────┼────────────────┤
│15 │Erg │Sylvia │
├──────────┼─────────────────────┼────────────────┤
│20 │Goldberg │Stephanie │
├──────────┼─────────────────────┼────────────────┤
│25 │Harris │Stephanie │
├──────────┼─────────────────────┼────────────────┤
│88 │Rosenberg │Samuel │
├──────────┼─────────────────────┼────────────────┤
│98 │Smith │Sara │
├──────────┼─────────────────────┼────────────────┤
│115 │Williams │Susan │
├──────────┼─────────────────────┼────────────────┤
│130 │Young │Stuart │
╰──────────┴─────────────────────┴────────────────╯
Example B
在本例中,您正在搜索姓氏中包含字符“erg”的所有宠物主人。
SELECT * FROM pet_owners WHERE lastname LIKE ‘%erg%’ ALLOW FILTERING;
╭──────────┬─────────────────────┬────────────────╮
│ID │LastName │FirstName │
├──────────┼─────────────────────┼────────────────┤
│11 │Berger │David │
├──────────┼─────────────────────┼────────────────┤
│18 │Gerg │Lawrence │
├──────────┼─────────────────────┼────────────────┤
│20 │Goldberg │Stephanie │
├──────────┼─────────────────────┼────────────────┤
│88 │Rosenberg │Samuel │
├──────────┼─────────────────────┼────────────────┤
│91 │Schulberg │Barry │
├──────────┼─────────────────────┼────────────────┤
│110 │Weinberg │Stuart │
╰──────────┴─────────────────────┴────────────────╯
注意,这个查询不会返回:(因为它是区分大小写的)
╭──────────┬─────────────────────┬────────────────╮
│ID │LastName │FirstName │
├──────────┼─────────────────────┼────────────────┤
│15 │Erg │Sylvia │
╰──────────┴─────────────────────┴────────────────╯
Example C
该表包含一些常用的LIKE
过滤器 和 过滤器返回的匹配项。
Filter | Matches |
---|---|
%abe% | Babel, aberration, cabernet, scarabees |
_0% | 10%, 20%, 50% |
a%t | asphalt, adapt, at |
INSERT
插入一行数据使用INSERT
语句完成:
insert_statement: INSERT INTO table_name ( `names_values` | `json_clause` )
: [ IF NOT EXISTS ]
: [ USING `update_parameter` ( AND `update_parameter` )* ];
names_values: `names` VALUES `tuple_literal`
json_clause: JSON `string` [ DEFAULT ( NULL | UNSET ) ]
names: '(' `column_name` ( ',' `column_name` )* ')'
update_parameter: ( TIMESTAMP `int_value` | TTL `int_value` | TIMEOUT `duration` )
int_value: ( `integer` | `bind_marker` )
例如:
INSERT INTO NerdMovies (movie, director, main_actor, year)
VALUES ('Serenity', 'Joss Whedon', 'Nathan Fillion', 2005)
USING TTL 86400 IF NOT EXISTS;
INSERT
语句为表中的给定行写入一个或多个列。请注意,由于一行是由其PRIMARY KEY
标识的,因此至少必须指定组成该列。要插入的列必须在使用VALUES
语法时提供。
注意,与SQL不同的是,INSERT
默认情况下不检查该行之前是否存在:如果之前不存在该行,则创建该行,否则更新该行。此外,没有办法知道哪个行创建或更新了。
INSERT
的所有更新都是自动应用的,意味着该语句不能对数据库状态产生部分影响。
但是,由于时间戳冲突事件的最终一致性语义,它可以保留一些列不变:在不同集群节点上并发发生的INSERT
语句无需协调即可继续。最终由具有最新时间戳的语句提供的单元格值将被接收 (参见更新顺序)。
默认情况下,Scylla将自动为INSERT
指定的每列生成一个微秒精度的时间戳,除非客户端提供时间戳。Scylla确保同一节点创建的时间戳是唯一的。在不同节点分配的时间戳不能保证全局唯一。对于稳定的高写速率,时间戳冲突并非不可能发生。如果发生冲突,即两个INSERTS
具有相同的时间戳,则冲突解决算法确定插入的单元格(参见更新顺序)。
有关的更多信息,请参考UPDATE一节中update_parameter
INSERT INTO NerdMovies (movie, director, main_actor)
VALUES ('Serenity', 'Anonymous', 'Unknown')
USING TIMESTAMP 1442880000000000;
INSERT INTO NerdMovies (movie, director, main_actor)
VALUES ('Serenity', 'Joseph Whedon', 'Nathan Fillion')
USING TIMESTAMP 1442880000000000;
SELECT movie, director, main_actor FROM NerdMovies WHERE movie = 'Serenity'
movie | director | main_actor | year
----------+---------------+------------+------
Serenity | Joseph Whedon | Unknown | null
INSERT
不需要分配所有列,所以如果两个语句修改了相同的主键,但分配了不同的列,后一语句将覆盖前一语句的相同部分:
INSERT INTO NerdMovies (movie, director, main_actor)
VALUES ('Serenity', 'Joss Whedon', 'Nathan Fillion');
INSERT INTO NerdMovies (movie, director, main_actor, year)
VALUES ('Serenity', 'Josseph Hill Whedon', 2005);
SELECT * FROM NerdMovies WHERE movie = 'Serenity'
╭─────────┬───────────────────┬──────────────┬─────╮
│movie │director │main_actor │year │
├─────────┼───────────────────┼──────────────┼─────┤
│Serenity │Joseph Hill Whedon │Nathan Fillion│2005 │
╰─────────┴───────────────────┴──────────────┴─────╯
还要注意,INSERT
不支持计数器,而UPDATE
支持。
NOTE:
Scylla Open Source 3.2新增功能,您可以在INSERT
语句中使用IF NOT EXISTS
。当使用这个条件时,只有在插入之前该行不存在时才会进行插入。每个这样的INSERT
都获得一个全局唯一的时间戳。使用IF NOT EXISTS
会产生一个不可忽略的性能开销 (在内部,因为Paxos将被使用),所以适当的使用IF NOT EXISTS
。
从Scylla Open Source 3.2开始,如果在表上启用,您可以对更改数据捕获(Change Data Capture, CDC) 表使用 UPDATE、INSERT 和 DELETE 语句。请注意,该功能在3.2版本中是实验的。
UPDATE
更新行是使用UPDATE
语句完成的:
update_statement: UPDATE `table_name`
: [ USING `update_parameter` ( AND `update_parameter` )* ]
: SET `assignment` ( ',' `assignment` )*
: WHERE `where_clause`
: [ IF ( EXISTS | `condition` ( AND `condition` )*) ]
update_parameter: ( TIMESTAMP `int_value` | TTL `int_value` | TIMEOUT `duration` )
int_value: ( `integer` | `bind_marker` )
assignment: `simple_selection` '=' `term`
: | `column_name` '=' `column_name` ( '+' | '-' ) `term`
: | `column_name` '=' `list_literal` '+' `column_name`
simple_selection: `column_name`
: | `column_name` '[' `term` ']'
: | `column_name` '.' `field_name`
condition: `simple_selection` `operator` `term`
例如:
UPDATE NerdMovies USING TTL 400
SET director = 'Joss Whedon',
main_actor = 'Nathan Fillion',
year = 2005
WHERE movie = 'Serenity';
UPDATE UserActions
SET total = total + 2
WHERE user = B70DE1D0-9908-4AE3-BE34-5573E5B09F14
AND action = 'click';
UPDATE
语句为表中的给定行写入一个或多个列。where_clause
子句用于选择要更新的行,并且必须包含构成PRIMARY KEY
的所有列。然后使用SET
关键字设置非主键列。
请注意,与SQL不同的是,UPDATE
默认情况下不检查行是否存在。(除了通过IF,见下文):如果之前不存在,则创建该行,否则更新该行。此外,没有办法知道是否发生了创建或更新。
在UPDATE
语句中,将自动应用同一分区键中的所有更新,这意味着要么存储所有提供的值,要么不存储任何值。
与INSERT
类似,在不同集群节点上并发发生的UPDATE
语句不需要协调。由具有最高时间戳的语句提供的单元格值将优先。如果两个UPDATE语句或UPDATE和INSERT语句具有相同的时间戳,则使用冲突解决算法确定哪个单元格优先 (请参阅更新顺序)。
关于赋值 assignment
:
C = C +3
用于增加/减少计数器。'='符号之后的列名必须与'='符号之前的列名相同。请注意,递增/递减只允许在计数器上进行,并且是计数器上唯一允许的更新操作。有关详细信息,请参阅有关计数器的部分。id = id + <some-collection>
和id [value1] = value2
为集合,详见相关章节。id.field = 3
用于设置非冻结的用户定义类型的字段值。
IF condition
Scylla Open Source 3.2 新特性
但是,可以通过IF
在某些列上使用条件,在这种情况下,除非满足条件,否则不会更新行。每个这样的UPDATE
都会获得一个全局唯一的时间戳。但是,请注意,使用IF
条件将产生不可忽略的性能成本 (在内部,将使用Paxos) ,因此应该谨慎使用。
Update parameters
UPDATE
、INSERT
(TIMESTAMP
语句支持DELETE
和BATCH
)语句支持以下参数:
TIMESTAMP
:设置操作的时间戳。如果未指定,协调器将使用语句执行开始时的当前时间(以微秒为单位)作为时间戳,从Unix纪元开始(1970年1月1日00:00:00 UTC)。这通常是一个合适的默认值。当不存在USING TIMESTAMP
更新参数时,INSERT、UPDATE、DELETE 或 BATCH语句应提供一个唯一的时间戳值,类似于协调器默认隐式设置的时间戳值。Scylla确保同一协调器节点创建的查询时间戳是唯一的 (即使在同一节点上的不同分片上也是如此) 。但是,在不同节点上分配的时间戳并不能保证全局唯一。请注意,在稳定的高写速率下,时间戳冲突并非不可能发生。如果发生冲突,例如两个INSERTS
具有相同的时间戳,则使用冲突解决算法确定插入的单元格 (参见更新排序了解更多信息):TTL
:为插入的值指定可选的生存时间 (单位为秒)。如果设置了,插入的值会在指定的时间后自动从数据库中删除。注意,TTL与插入的值有关,而与列本身无关。这意味着该列的任何后续更新也将重置TTL (为该更新中指定的TTL)。默认情况下,值永远不会过期。TTL为0等于没有TTL。如果表具有default_time_to_live,则TTL为0将删除插入或更新值的TTL。TTL为null相当于插入TTL为0。您可以在文档和Scylla大学课程中阅读更多关于TTL的信息。TIMEOUT
:指定特定请求的超时时间。请参考SELECT部分了解更多信息。
Update ordering
INSERT、UPDATE 和 DELETE 操作由他们的TIMESTAMP
排序。
这些更改的排序是在单元级别完成的,其中每个单元都带有一个写TIMESTAMP
;当它具有非零生存时间 (TTL) 时,其他属性与过期相关;一个单元值。
对在给定行和列中插入、更新或删除数据的单元格进行排序的基本规则是,时间戳最高的单元格。
然而,有可能多个这样的单元格会携带相同的TIMESTAMP
。TIMESTAMP
冲突可能有几个原因:
- 良性冲突可以由突变的“重播”引起,例如,由于客户端重试,或由于内部进程。在这种情况下,单元格是等效的,可以任意选择其中的任何一个。
TIMESTAMP
冲突 通常可能是由不同协调器节点并行查询引起的。协调器可以基于它们的本地时间(以微秒为单位)计算相同的写TIMESTAMP
。- 如果应用程序不使用
USING TIMESTAMP
参数保证唯一的时间戳,则用户提供的时间戳也可能发生冲突 (请参阅更新参数以获取更多信息)。
如上所述,在重播的情况下,单元格的顺序应该无关紧要,因为它们携带相同的值和相同的过期属性,因此选择其中的任何一个都将达到相同的结果。但是,其他TIMESTAMP
冲突必须由所有节点以一致的方式解决。否则,如果节点在冲突的情况下选择了一个任意的cell,它们会得到不同的结果,从不同的副本读取将检测到不一致并触发读取修复,这将产生另一个仍然与现有cell冲突的cell,无法保证收敛。
因此,Scylla实现了一个内部的,一致的冲突解决算法,该算法基于其他属性对具有冲突TIMESTAMP
值的单元格排序,例如:无论单元格,是 tombstone 墓碑还是实时单元格;单元格是否有失效时间;单元格的TTL;最后是单元格的值。
冲突解决算法记录在Scylla的内部文档中,可能会有更改。
可靠的序列化可以通过使用唯一的写TIMESTAMP
和使用轻量级事务(LWT)来确保INSERT、UPDATE 和 DELETE 的原子性来实现。
DELETE
删除一行或几行使用DELETE
语句:
delete_statement: DELETE [ `simple_selection` ( ',' `simple_selection` ) ]
: FROM `table_name`
: [ USING `update_parameter` ( AND `update_parameter` )* ]
: WHERE `where_clause`
: [ IF ( EXISTS | `condition` ( AND `condition` )* ) ]
例如:
DELETE FROM NerdMovies USING TIMESTAMP 1240003134000000
WHERE movie = 'Serenity';
DELETE phone FROM Users
WHERE userid IN (C73DE1D3-AF08-40F3-B124-3FF3E5109F22, B70DE1D0-9908-4AE3-BE34-5573E5B09F14);
DELETE
语句用于删除列和行。如果在DELETE
关键字之后直接提供了列名,那么只有这些列会从WHERE
子句所指示的行中删除。否则,将删除整行。
WHERE
子句指定要删除哪些行。通过使用IN
操作符,可以用一条语句删除多行。可以使用不等式运算符(如>=
)删除一个范围内的行。
DELETE
支持TIMESTAMP选项,其语义与UPDATE
语句中使用的TIMESTAMP
参数相同。DELETE
语句删除使用小于或等于DELETE
时间戳的INSERT或UPDATE (或BATCH)写入的数据。有关update_parameter
的更多信息,请参阅UPDATE部分。
在DELETE
语句中,将自动应用同一分区键中的所有删除,这意味着要么删除语句中提到的所有列,要么不删除列。如果DELETE
语句与同一主键的INSERT
或UPDATE
具有相同的时间戳,则优先执行DELETE
操作 (参见更新顺序)。
通过使用IF
子句,DELETE
操作可以是有条件的,类似于UPDATE
和INSERT
语句。每个这样的DELETE
都有一个全局唯一的时间戳。但是,与INSERT
和UPDATE
语句一样,这将产生不可忽略的性能成本 (在内部,将使用Paxos),因此应该谨慎使用。
Range deletions
范围删除( Range deletions )允许您从单个分区中删除行,前提是集群键在给定的范围内。删除请求可以在端设备确定,也可以是开放式的。
Open range deletions
Scylla Open Source 3.2 新特性
Open range deletions (开放范围删除)基于开放式请求(>、<、>=、=<等)删除行。
例如,假设您的 Madison Square Garden 事件的数据集包括:
CREATE KEYSPACE mykeyspace WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1'} AND durable_writes = true;
use mykeyspace ;
CREATE TABLE events ( id text, created_at date, content text, PRIMARY KEY (id, created_at) );
INSERT into events (id, created_at, content) VALUES ('concert', '2019-11-19', 'SuperM');
INSERT into events (id, created_at, content) VALUES ('concert', '2019-11-15', 'Billy Joel');
INSERT into events (id, created_at, content) VALUES ('game', '2019-11-03', 'Knicks v Sacramento');
INSERT into events (id, created_at, content) VALUES ('concert', '2019-10-31', 'Dead & Company');
INSERT into events (id, created_at, content) VALUES ('game', '2019-10-28', 'Knicks v Chicago');
INSERT into events (id, created_at, content) VALUES ('concert', '2019-10-25', 'Billy Joel');
SELECT * from events;
id | created_at | content
---------+------------+---------------------
game | 2019-10-28 | Knicks v Chicago
game | 2019-11-03 | Knicks v Sacramento
concert | 2019-10-25 | Billy Joel
concert | 2019-10-31 | Dead & Company
concert | 2019-11-15 | Billy Joel
concert | 2019-11-19 | SuperM
(6 rows)
你想删除十月份所有的音乐会使用开放式范围删除。你可以运行:
DELETE FROM events WHERE id='concert' AND created_at <= '2019-10-31';
SELECT * from events;
id | created_at | content
---------+------------+---------------------
game | 2019-10-28 | Knicks v Chicago
game | 2019-11-03 | Knicks v Sacramento
concert | 2019-11-15 | Billy Joel
concert | 2019-11-19 | SuperM
(4 rows)
BATCH
多个INSERT
、UPDATE
和DELETE
可以通过BATCH
语句分组在一条语句中执行:
batch_statement: BEGIN [ UNLOGGED | COUNTER ] BATCH
: [ USING `update_parameter` ( AND `update_parameter` )* ]
: `modification_statement` ( ';' `modification_statement` )*
: APPLY BATCH
modification_statement: `insert_statement` | `update_statement` | `delete_statement`
例如:
BEGIN BATCH
INSERT INTO users (userid, password, name) VALUES ('user2', 'ch@ngem3b', 'second user');
UPDATE users SET password = 'ps22dhds' WHERE userid = 'user3';
INSERT INTO users (userid, password) VALUES ('user4', 'ch@ngem3c');
DELETE name FROM users WHERE userid = 'user1';
APPLY BATCH;
BATCH
语句将多个修改语句 (插入/更新和删除) 分组到单个语句中。它有几个用途:
- 当批量处理多个更新时,它节省了客户端和服务器之间 (有时是服务器协调器和副本之间) 的网络往返。
- 属于给定分区键的
BATCH
中的所有更新都是自动执行的。 - 默认情况下,批处理中的所有操作都作为日志执行,以确保所有的变化最终完成(或者没有)。参见UNLOGGED batches注释
注意:
- 批处理语句只能包含
UPDATE
、INSERT
和DELETE
语句 (例如,不能包含其他批处理语句)。 - 批处理并不完全类似于SQL事务。
- 如果没有为每个操作指定时间戳,那么所有操作将应用相同的时间戳 (要么是自动生成的时间戳,要么是批处理级别提供的时间戳)。由于Scylla在时间戳关系的情况下的冲突解决过程,操作的应用顺序可能与它们在
BATCH
语句中列出的顺序不同。要强制执行特定的操作顺序,必须指定每个操作的时间戳。 - 作为优化,单个分区的 LOGGED 批处理将被转换为 UNLOGGED 批处理。
BATCH
支持TIMESTAMP
选项,其语义与UPDATE
语句中的TIMESTAMP
参数相同。有关update_parameter的更多信息,请参阅UPDATE部分。
UNLOGGED batches
默认情况下,Scylla 使用批处理日志来确保批处理中的所有操作最终完成或不完成(但是请注意,操作仅在单个分区中隔离)。
当批处理跨越多个分区时,批处理原子性会有性能损失。如果您不想造成这种错误,可以使用UNLOGGED
选项,跳过批处理日志。如果使用了UNLOGGED
选项,则批处理操作可能只会使部分补丁应用失败。
COUNTER batches
使用COUNTER
选项进行批量计数器更新。与Scylla中的其他更新不同,计数器更新不是幂等的。
Apache Cassandra Query Language (CQL) Reference