Cassandra的資料模型類似於關係型資料庫的模型,且提供了與SQL語言非常類似的CQL語言進行操作。
但是Cassandra的資料模型類似於多層鍵值對結構,與關係型資料庫存在巨大差別。
本文基於: [cqlsh 5.0.1 | Cassandra 3.11.2 | CQL spec 3.4.4 | Native protocol v4]
目錄:
多層KV結構
Cassandra 的資料模型由 keyspace (類似關係型資料庫裡的database), column family(類似關係型資料庫裡的table), 主鍵(key)和列(column)組成。
對於一個 column family 不應該想象成關係型資料庫中的表, 而是一個多層的key-value結構:
Map<PartitionKey, SortedMap<ClusteringKey, Column>>
我們使用CQL來描述:
create table table1 (
key1 int,
key2 int,
content text,
PRIMARY KEY ((key1), key2)
);
在上述CQL建立的表(column family)中,key1是 partition key, 而 key2 是 clustering key, key1 和 key2 稱為主鍵(PRIMARY KEY)
Cassandra支援更復雜的表結構:
CREATE TABLE table2 (
pkey1 int,
pkey2 int,
ckey1 int,
ckey2 int,
content text,
PRIMARY KEY ((pkey1, pkey2), ckey1, ckey2)
);
此時的資料結構可以描述為:
Map<pkey1, Map<pkey2, SortedMap<ckey1, SortedMap<ckey2, content>>>>
作為一個分散式資料庫, Cassandra 根據 partition key 決定資料如何在叢集的各個節點上分割槽。clustering key 決定資料在分割槽內的排序。
查詢
下文中將以 table2 為例介紹 cassadra 資料模型的特性。
從上文使用Map描述的表結構可知,我們無法根據非主鍵進行查詢(如table2中的data):
SELECT * FROM table2 WHERE content='a'; -- error
SELECT * FROM table2 WHERE pkey1 = 1 AND content='a'; -- error
SELECT * FROM table2 WHERE pkey1 = 1 AND pkey2 = 1; -- right
通常情況下,在對形如((pkey1, pkey2), ckey1, ckey2)這樣的主鍵列進行查詢時需要注意:
- partition key 僅支援精確查詢(=, in), 不能進行範圍查詢(>, <, >=, <=)。注: Cassandra 不支援 != 查詢。
- 涉及多個 partition key 的查詢必須提供前置 partition key 的精確值。即若要查詢 pkey2 則必須提供 pkey1 的精確值。
- 涉及 clustering key 的查詢,必須提供所有 partition key 的精確值
- 涉及的 clustering key 不能跳躍,若要根據 ckey2 進行查詢則必須提供 ckey1 的精確值
(請不要記憶上述結論,根據Cassandra的內部資料結構很容易明白可以進行什麼樣的查詢)
下面根據具體示例說明。
僅涉及partition key:
SELECT * FROM table2 WHERE pkey1 = 1; -- right
SELECT * FROM table2 WHERE pkey1=1 AND pkey2=1; -- right
SELECT * FROM table2 WHERE pkey2=1; -- error
涉及一個 clustering key:
SELECT * FROM table2 WHERE pkey1=1 AND pkey2=1 AND ckey1>0; -- right
SELECT * FROM table2 WHERE pkey1=1 AND ckey1>0; -- error
SELECT * FROM table2 WHERE ckey1=1; -- error
涉及多個 clusterin key:
SELECT * FROM table2 WHERE pkey1=1 AND pkey2=1 AND ckey1=1 AND ckey2>0; -- right
SELECT * FROM table2 WHERE pkey1=1 AND pkey2=1 AND ckey2>0; -- error
SELECT * FROM table2 WHERE pkey1=1 AND pkey2=1 AND ckey1>0 AND ckey2=1; --error
排序
Cassandra 支援查詢結果按照 clustering key 進行排序,不過排序功能也非常有限:
SELECT * FROM table2 WHERE pkey1=1 AND pkey2=1 ORDER BY ckey1; -- right
SELECT * FROM table2 WHERE pkey1=1 AND pkey2=1 ORDER BY ckey1, ckey2; -- right
SELECT * FROM table2 WHERE pkey1=1 AND pkey2=1 ORDER BY ckey2; -- error
使用排序功能和涉及 clustering key 的查詢一樣, 必須提供所有 partition key 的精確值(= 或 in 運算子)。這是因為 Cassandra 僅支援單個節點上資料的排序。
涉及多個 clustering key 的排序必須按照 clustering key 的順序進行排序不能跳躍, 即可以ORDER BY ckey1, ckey2
, 不能ORDER BY ckey2
。
預設情況下只能進行升序排列,即ORDER BY ckey1 ASC
, ORDER BY ckey2
。
這是因為Cassandra只能根據每個節點上"SortedMap"固有的順序排列查詢結果,不過我們可以在建立表時自定義排序規則:
CREATE TABLE table2 (
pkey1 int,
pkey2 int,
ckey1 int,
ckey2 int,
content text,
PRIMARY KEY ((pkey1, pkey2), ckey1, ckey2)
) WITH CLUSTERING ORDER BY(ckey1 DESC, ckey2 ASC);
聚合
Cassandra 允許根據主鍵列定義的順序進行聚合:
SELECT count(*) FROM table2 GROUP BY pkey1; -- right
SELECT count(*) FROM table2 GROUP BY pkey1, pkey2; -- right
SELECT count(*) FROM table2 GROUP BY pkey1, pkey2, ckey1, ckey2; -- right
SELECT count(*) FROM table2 WHERE pkey1=1 GROUP BY pkey2; -- error
這種聚合可以對多個節點上的資料進行聚合處理。
對於帶有WHERE條件的查詢, Cassandra 僅支援對單個節點上的資料進行聚合,就是說必須提供 partition key 的精確值才能進行聚合:
SELECT count(*) FROM table2 WHERE pkey1=1 AND pkey2=1 GROUP BY ckey1; -- right
SELECT count(*) FROM table2 WHERE pkey1=1 AND pkey2=1 GROUP BY ckey1, ckey2; -- right
SELECT count(*) FROM table2 WHERE pkey1=1 AND pkey2=1 GROUP BY ckey2; -- error
SELECT count(*) FROM table2 WHERE pkey1=1 GROUP BY pkey2; -- error
這一點與 Cassandra 查詢時的特徵是一致的。
Cassandra 支援 sum, min, max, count, distinct 等聚合功能, 不支援HAVING語句。
ALLOW FILTERING
上文我們提到一些 Cassandra 不支援的查詢:
SELECT * FROM table2 WHERE ckey1=1;
可以看到 Cassandra 的報錯資訊:
Cannot execute this query as it might involve data filtering and thus may have unpredictable performance. If you want to execute this query despite the performance unpredictability, use ALLOW FILTERING
對於 Cassandra 多層巢狀KV的資料結構來說,不可能通過 key 查詢到相應資料,只能搜尋所有的資料來完成此查詢。
對於一個有100萬條資料表而言,ckey1=1的記錄可能僅佔5%, 此時 Cassandra 仍可以在可接受的時間內完成查詢。但是,Cassandra 並不瞭解此查詢需要搜尋所有資料,因此需要操作者使用 ALLOW FILTERING 允許 Cassandra 掃描所有資料:
SELECT * FROM table2 WHERE ckey1=1 ALLOW FILTERING;
Cassandra 官方對於ALLOW FILTERING 進行了非常詳盡的說明, 可以參考ALLOW FILTERING explained。
作者提醒, ALLOW FILTERING 可能消耗大量時間和資源,請謹慎在生產環境下使用此功能。
次級索引
除了主鍵列之外我們可以為 clustering key 和普通的值建立次級索引(secondary index)。
次級索引是一個另外的key-value對映, 可以根據索引列直接查詢到資料。
建立索引:
CREATE INDEX idx_ckey1 on table2(ckey1);
CREATE INDEX idx_content on table2(content);
使用索引進行查詢:
SELECT * FROM table2 WHERE ckey1=1; -- right
SELECT * FROM table2 WHERE content='a'; -- right
SELECT * FROM table2 WHERE ckey1>0; -- error
SELECT * FROM table2 WHERE pkey1=1 AND ckey1=1; --error
索引僅支援單獨、精確查詢, 不支援範圍查詢或者與主鍵(其它索引)聯合查詢。
請閱讀官方文件When to use an index,瞭解索引的使用場景。